diff --git a/samples/SocialSample/Startup.cs b/samples/SocialSample/Startup.cs index 0b76698aa..972f3ccc0 100644 --- a/samples/SocialSample/Startup.cs +++ b/samples/SocialSample/Startup.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using System.Net.Http; using System.Net.Http.Headers; @@ -5,6 +6,7 @@ using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNet.Authentication.Cookies; +using Microsoft.AspNet.Authentication.Facebook; using Microsoft.AspNet.Authentication.Google; using Microsoft.AspNet.Authentication.MicrosoftAccount; using Microsoft.AspNet.Authentication.OAuth; @@ -30,6 +32,24 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) { loggerfactory.AddConsole(LogLevel.Information); + // Simple error page to avoid a repo dependency. + app.Use(async (context, next) => + { + try + { + await next(); + } + catch (Exception ex) + { + if (context.Response.HasStarted) + { + throw; + } + context.Response.StatusCode = 500; + await context.Response.WriteAsync(ex.ToString()); + } + }); + app.UseCookieAuthentication(options => { options.AutomaticAuthenticate = true; @@ -38,10 +58,12 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) }); // https://developers.facebook.com/apps/ - app.UseFacebookAuthentication(options => + app.UseFacebookAuthentication(new FacebookOptions() { - options.AppId = "569522623154478"; - options.AppSecret = "a124463c4719c94b4228d9a240e5dc1a"; + AppId = "569522623154478", + AppSecret = "a124463c4719c94b4228d9a240e5dc1a", + Scope = { "email" }, + Fields = { "name", "email" }, }); app.UseOAuthAuthentication(new OAuthOptions diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookDefaults.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookDefaults.cs index 4877b25fc..d2896a5ce 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookDefaults.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookDefaults.cs @@ -7,10 +7,10 @@ public static class FacebookDefaults { public const string AuthenticationScheme = "Facebook"; - public static readonly string AuthorizationEndpoint = "https://www.facebook.com/v2.2/dialog/oauth"; + public static readonly string AuthorizationEndpoint = "https://www.facebook.com/v2.5/dialog/oauth"; - public static readonly string TokenEndpoint = "https://graph.facebook.com/v2.2/oauth/access_token"; + public static readonly string TokenEndpoint = "https://graph.facebook.com/v2.5/oauth/access_token"; - public static readonly string UserInformationEndpoint = "https://graph.facebook.com/v2.2/me"; + public static readonly string UserInformationEndpoint = "https://graph.facebook.com/v2.5/me"; } } diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookHandler.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookHandler.cs index e25fef496..d39038471 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookHandler.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Globalization; using System.Net.Http; using System.Security.Claims; @@ -10,8 +9,6 @@ using System.Threading.Tasks; using Microsoft.AspNet.Authentication.OAuth; using Microsoft.AspNet.Http.Authentication; -using Microsoft.AspNet.Http.Extensions; -using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.WebUtilities; using Newtonsoft.Json.Linq; @@ -24,31 +21,6 @@ public FacebookHandler(HttpClient httpClient) { } - protected override async Task ExchangeCodeAsync(string code, string redirectUri) - { - var queryBuilder = new QueryBuilder() - { - { "grant_type", "authorization_code" }, - { "code", code }, - { "redirect_uri", redirectUri }, - { "client_id", Options.AppId }, - { "client_secret", Options.AppSecret }, - }; - - var response = await Backchannel.GetAsync(Options.TokenEndpoint + queryBuilder.ToString(), Context.RequestAborted); - response.EnsureSuccessStatusCode(); - - var form = new FormCollection(FormReader.ReadForm(await response.Content.ReadAsStringAsync())); - var payload = new JObject(); - foreach (string key in form.Keys) - { - payload.Add(string.Equals(key, "expires", StringComparison.OrdinalIgnoreCase) ? "expires_in" : key, (string)form[key]); - } - - // The refresh token is not available. - return OAuthTokenResponse.Success(payload); - } - protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { var endpoint = QueryHelpers.AddQueryString(Options.UserInformationEndpoint, "access_token", tokens.AccessToken); @@ -56,6 +28,10 @@ protected override async Task CreateTicketAsync(ClaimsIden { endpoint = QueryHelpers.AddQueryString(endpoint, "appsecret_proof", GenerateAppSecretProof(tokens.AccessToken)); } + if (Options.Fields.Count > 0) + { + endpoint = QueryHelpers.AddQueryString(endpoint, "fields", string.Join(",", Options.Fields)); + } var response = await Backchannel.GetAsync(endpoint, Context.RequestAborted); response.EnsureSuccessStatusCode(); diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookOptions.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookOptions.cs index aa829cb37..0bf6a3716 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookOptions.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookOptions.cs @@ -1,8 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNet.Http; +using System.Collections.Generic; using Microsoft.AspNet.Authentication.OAuth; +using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Authentication.Facebook { @@ -24,6 +25,7 @@ public FacebookOptions() TokenEndpoint = FacebookDefaults.TokenEndpoint; UserInformationEndpoint = FacebookDefaults.UserInformationEndpoint; SaveTokensAsClaims = false; + Fields = new List(); } // Facebook uses a non-standard term for this field. @@ -51,5 +53,11 @@ public string AppSecret /// This is enabled by default. /// public bool SendAppSecretProof { get; set; } + + /// + /// The list of fields to retrieve from the UserInformationEndpoint. + /// https://developers.facebook.com/docs/graph-api/reference/user + /// + public IList Fields { get; } } } diff --git a/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs index 21ace654e..c1796c3de 100644 --- a/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs @@ -86,7 +86,7 @@ public async Task NestedMapWillNotAffectRedirect() var transaction = await server.SendAsync("http://example.com/base/login"); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); var location = transaction.Response.Headers.Location.AbsoluteUri; - Assert.Contains("https://www.facebook.com/v2.2/dialog/oauth", location); + Assert.Contains("https://www.facebook.com/v2.5/dialog/oauth", location); Assert.Contains("response_type=code", location); Assert.Contains("client_id=", location); Assert.Contains("redirect_uri=" + UrlEncoder.Default.Encode("http://example.com/base/signin-facebook"), location); @@ -113,7 +113,7 @@ public async Task MapWillNotAffectRedirect() var transaction = await server.SendAsync("http://example.com/login"); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); var location = transaction.Response.Headers.Location.AbsoluteUri; - Assert.Contains("https://www.facebook.com/v2.2/dialog/oauth", location); + Assert.Contains("https://www.facebook.com/v2.5/dialog/oauth", location); Assert.Contains("response_type=code", location); Assert.Contains("client_id=", location); Assert.Contains("redirect_uri="+ UrlEncoder.Default.Encode("http://example.com/signin-facebook"), location); @@ -147,7 +147,7 @@ public async Task ChallengeWillTriggerRedirection() var transaction = await server.SendAsync("http://example.com/challenge"); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); var location = transaction.Response.Headers.Location.AbsoluteUri; - Assert.Contains("https://www.facebook.com/v2.2/dialog/oauth", location); + Assert.Contains("https://www.facebook.com/v2.5/dialog/oauth", location); Assert.Contains("response_type=code", location); Assert.Contains("client_id=", location); Assert.Contains("redirect_uri=", location); @@ -178,11 +178,11 @@ public async Task CustomUserInfoEndpointHasValidGraphQuery() if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) == FacebookDefaults.TokenEndpoint) { var res = new HttpResponseMessage(HttpStatusCode.OK); - var tokenResponse = new Dictionary + var graphResponse = JsonConvert.SerializeObject(new { - { "access_token", "TestAuthToken" }, - }; - res.Content = new FormUrlEncodedContent(tokenResponse); + access_token = "TestAuthToken" + }); + res.Content = new StringContent(graphResponse, Encoding.UTF8); return res; } if (req.RequestUri.GetComponents(UriComponents.SchemeAndServer | UriComponents.Path, UriFormat.UriEscaped) ==