diff --git a/samples/CookieSample/Startup.cs b/samples/CookieSample/Startup.cs index 04694b7cf..6fd616bd7 100644 --- a/samples/CookieSample/Startup.cs +++ b/samples/CookieSample/Startup.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Security.Claims; using Microsoft.AspNet.Authentication.Cookies; using Microsoft.AspNet.Builder; @@ -25,10 +26,11 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) app.Run(async context => { - if (string.IsNullOrEmpty(context.User.Identity.Name)) + if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) { - var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") })); + var user = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, "bob") }, CookieAuthenticationDefaults.AuthenticationScheme)); await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, user); + context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("Hello First timer"); return; diff --git a/samples/CookieSessionSample/Startup.cs b/samples/CookieSessionSample/Startup.cs index 5858c2c39..1bfc635cc 100644 --- a/samples/CookieSessionSample/Startup.cs +++ b/samples/CookieSessionSample/Startup.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using System.Security.Claims; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; @@ -27,7 +28,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) app.Run(async context => { - if (string.IsNullOrEmpty(context.User.Identity.Name)) + if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) { // Make a large identity var claims = new List(1001); @@ -36,7 +37,10 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) { claims.Add(new Claim(ClaimTypes.Role, "SomeRandomGroup" + i, ClaimValueTypes.String, "IssuedByBob", "OriginalIssuerJoe")); } - await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(new ClaimsIdentity(claims))); + + await context.Authentication.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme))); + context.Response.ContentType = "text/plain"; await context.Response.WriteAsync("Hello First timer"); return; diff --git a/samples/OpenIdConnectSample/Startup.cs b/samples/OpenIdConnectSample/Startup.cs index b07d87da5..3023a55aa 100644 --- a/samples/OpenIdConnectSample/Startup.cs +++ b/samples/OpenIdConnectSample/Startup.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNet.Builder; +using System.Linq; +using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Authentication; @@ -38,7 +39,7 @@ public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) app.Run(async context => { - if (string.IsNullOrEmpty(context.User.Identity.Name)) + if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) { await context.Authentication.ChallengeAsync(OpenIdConnectAuthenticationDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = "/" }); diff --git a/samples/SocialSample/Startup.cs b/samples/SocialSample/Startup.cs index 62d9c8380..bce45cb7a 100644 --- a/samples/SocialSample/Startup.cs +++ b/samples/SocialSample/Startup.cs @@ -1,3 +1,4 @@ +using System.Linq; using System.Net.Http; using System.Net.Http.Headers; using System.Security.Claims; @@ -110,6 +111,7 @@ dnx . web options.Caption = "MicrosoftAccount - Requires project changes"; options.ClientId = "00000000480FF62E"; options.ClientSecret = "bLw2JIvf8Y1TaToipPEqxTVlOeJwCUsr"; + options.Scope.Add("wl.emails"); }); // https://github.com/settings/applications/ @@ -131,48 +133,53 @@ dnx . web options.TokenEndpoint = "https://github.com/login/oauth/access_token"; options.UserInformationEndpoint = "https://api.github.com/user"; options.ClaimsIssuer = "OAuth2-Github"; + options.SaveTokensAsClaims = false; // Retrieving user information is unique to each provider. - options.Notifications = new OAuthAuthenticationNotifications() + options.Notifications = new OAuthAuthenticationNotifications { - OnGetUserInformationAsync = async (context) => + OnAuthenticated = async notification => { // Get the GitHub user - var userRequest = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); - userRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken); - userRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - var userResponse = await context.Backchannel.SendAsync(userRequest, context.HttpContext.RequestAborted); - userResponse.EnsureSuccessStatusCode(); - var text = await userResponse.Content.ReadAsStringAsync(); - var user = JObject.Parse(text); - - var identity = new ClaimsIdentity( - context.Options.AuthenticationScheme, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - - JToken value; - var id = user.TryGetValue("id", out value) ? value.ToString() : null; - if (!string.IsNullOrEmpty(id)) + var request = new HttpRequestMessage(HttpMethod.Get, notification.Options.UserInformationEndpoint); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", notification.AccessToken); + request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + var response = await notification.Backchannel.SendAsync(request, notification.HttpContext.RequestAborted); + response.EnsureSuccessStatusCode(); + + var user = JObject.Parse(await response.Content.ReadAsStringAsync()); + + var identifier = user.Value("id"); + if (!string.IsNullOrEmpty(identifier)) { - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, id, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + notification.Identity.AddClaim(new Claim( + ClaimTypes.NameIdentifier, identifier, + ClaimValueTypes.String, notification.Options.ClaimsIssuer)); } - var userName = user.TryGetValue("login", out value) ? value.ToString() : null; + + var userName = user.Value("login"); if (!string.IsNullOrEmpty(userName)) { - identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, userName, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + notification.Identity.AddClaim(new Claim( + ClaimsIdentity.DefaultNameClaimType, userName, + ClaimValueTypes.String, notification.Options.ClaimsIssuer)); } - var name = user.TryGetValue("name", out value) ? value.ToString() : null; + + var name = user.Value("name"); if (!string.IsNullOrEmpty(name)) { - identity.AddClaim(new Claim("urn:github:name", name, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + notification.Identity.AddClaim(new Claim( + "urn:github:name", name, + ClaimValueTypes.String, notification.Options.ClaimsIssuer)); } - var link = user.TryGetValue("url", out value) ? value.ToString() : null; + + var link = user.Value("url"); if (!string.IsNullOrEmpty(link)) { - identity.AddClaim(new Claim("urn:github:url", link, ClaimValueTypes.String, context.Options.ClaimsIssuer)); + notification.Identity.AddClaim(new Claim( + "urn:github:url", link, + ClaimValueTypes.String, notification.Options.ClaimsIssuer)); } - - context.Principal = new ClaimsPrincipal(identity); }, }; }); @@ -207,8 +214,8 @@ dnx . web { signoutApp.Run(async context => { - await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); context.Response.ContentType = "text/html"; + await context.Authentication.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await context.Response.WriteAsync(""); await context.Response.WriteAsync("You have been logged out. Goodbye " + context.User.Identity.Name + "
"); await context.Response.WriteAsync("Home"); @@ -219,7 +226,7 @@ dnx . web // Deny anonymous request beyond this point. app.Use(async (context, next) => { - if (string.IsNullOrEmpty(context.User.Identity.Name)) + if (!context.User.Identities.Any(identity => identity.IsAuthenticated)) { // The cookie middleware will intercept this 401 and redirect to /login await context.Authentication.ChallengeAsync(); @@ -233,7 +240,7 @@ dnx . web { context.Response.ContentType = "text/html"; await context.Response.WriteAsync(""); - await context.Response.WriteAsync("Hello " + context.User.Identity.Name + "
"); + await context.Response.WriteAsync("Hello " + (context.User.Identity.Name ?? "anonymous") + "
"); foreach (var claim in context.User.Claims) { await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "
"); diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHandler.cs index 291956409..cc9c1fdcf 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHandler.cs @@ -17,14 +17,14 @@ namespace Microsoft.AspNet.Authentication.Facebook { - internal class FacebookAuthenticationHandler : OAuthAuthenticationHandler + internal class FacebookAuthenticationHandler : OAuthAuthenticationHandler { public FacebookAuthenticationHandler(HttpClient httpClient) : base(httpClient) { } - protected override async Task ExchangeCodeAsync(string code, string redirectUri) + protected override async Task ExchangeCodeAsync(string code, string redirectUri) { var queryBuilder = new QueryBuilder() { @@ -35,70 +35,78 @@ protected override async Task ExchangeCodeAsync(string code, stri { "client_secret", Options.AppSecret }, }; - var tokenResponse = await Backchannel.GetAsync(Options.TokenEndpoint + queryBuilder.ToString(), Context.RequestAborted); - tokenResponse.EnsureSuccessStatusCode(); - var oauthTokenResponse = await tokenResponse.Content.ReadAsStringAsync(); + var response = await Backchannel.GetAsync(Options.TokenEndpoint + queryBuilder.ToString(), Context.RequestAborted); + response.EnsureSuccessStatusCode(); - var form = new FormCollection(FormReader.ReadForm(oauthTokenResponse)); - var response = new JObject(); + var form = new FormCollection(FormReader.ReadForm(await response.Content.ReadAsStringAsync())); + var payload = new JObject(); foreach (string key in form.Keys) { - response.Add(string.Equals(key, "expires", StringComparison.OrdinalIgnoreCase) ? "expires_in" : key, form[key]); + payload.Add(string.Equals(key, "expires", StringComparison.OrdinalIgnoreCase) ? "expires_in" : key, form[key]); } + // The refresh token is not available. - return new TokenResponse(response); + return new OAuthTokenResponse(payload); } - protected override async Task GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens) + protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { - var graphAddress = Options.UserInformationEndpoint + "?access_token=" + UrlEncoder.UrlEncode(tokens.AccessToken); + var endpoint = Options.UserInformationEndpoint + "?access_token=" + UrlEncoder.UrlEncode(tokens.AccessToken); if (Options.SendAppSecretProof) { - graphAddress += "&appsecret_proof=" + GenerateAppSecretProof(tokens.AccessToken); + endpoint += "&appsecret_proof=" + GenerateAppSecretProof(tokens.AccessToken); } - var graphResponse = await Backchannel.GetAsync(graphAddress, Context.RequestAborted); - graphResponse.EnsureSuccessStatusCode(); - var text = await graphResponse.Content.ReadAsStringAsync(); - var user = JObject.Parse(text); - - var context = new FacebookAuthenticatedContext(Context, Options, user, tokens); - var identity = new ClaimsIdentity( - Options.ClaimsIssuer, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - if (!string.IsNullOrEmpty(context.Id)) + var response = await Backchannel.GetAsync(endpoint, Context.RequestAborted); + response.EnsureSuccessStatusCode(); + + var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); + + var notification = new OAuthAuthenticatedContext(Context, Options, Backchannel, tokens, payload) + { + Properties = properties, + Principal = new ClaimsPrincipal(identity) + }; + + var identifier = FacebookAuthenticationHelper.GetId(payload); + if (!string.IsNullOrEmpty(identifier)) { - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.UserName)) + + var userName = FacebookAuthenticationHelper.GetUserName(payload); + if (!string.IsNullOrEmpty(userName)) { - identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.UserName, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, userName, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.Email)) + + var email = FacebookAuthenticationHelper.GetEmail(payload); + if (!string.IsNullOrEmpty(email)) { - identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.Name)) + + var name = FacebookAuthenticationHelper.GetName(payload); + if (!string.IsNullOrEmpty(name)) { - identity.AddClaim(new Claim("urn:facebook:name", context.Name, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim("urn:facebook:name", name, ClaimValueTypes.String, Options.ClaimsIssuer)); // Many Facebook accounts do not set the UserName field. Fall back to the Name field instead. - if (string.IsNullOrEmpty(context.UserName)) + if (string.IsNullOrEmpty(userName)) { - identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, context.Name, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(identity.NameClaimType, name, ClaimValueTypes.String, Options.ClaimsIssuer)); } } - if (!string.IsNullOrEmpty(context.Link)) + + var link = FacebookAuthenticationHelper.GetLink(payload); + if (!string.IsNullOrEmpty(link)) { - identity.AddClaim(new Claim("urn:facebook:link", context.Link, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim("urn:facebook:link", link, ClaimValueTypes.String, Options.ClaimsIssuer)); } - context.Properties = properties; - context.Principal = new ClaimsPrincipal(identity); - await Options.Notifications.Authenticated(context); + await Options.Notifications.Authenticated(notification); - return new AuthenticationTicket(context.Principal, context.Properties, context.Options.AuthenticationScheme); + return new AuthenticationTicket(notification.Principal, notification.Properties, notification.Options.AuthenticationScheme); } private string GenerateAppSecretProof(string accessToken) diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHelper.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHelper.cs new file mode 100644 index 000000000..3f9f98114 --- /dev/null +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationHelper.cs @@ -0,0 +1,40 @@ +// 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.Framework.Internal; +using Newtonsoft.Json.Linq; + +namespace Microsoft.AspNet.Authentication.Facebook +{ + /// + /// Contains static methods that allow to extract user's information from a + /// instance retrieved from Facebook after a successful authentication process. + /// + public static class FacebookAuthenticationHelper + { + /// + /// Gets the Facebook user ID. + /// + public static string GetId([NotNull] JObject user) => user.Value("id"); + + /// + /// Gets the user's name. + /// + public static string GetName([NotNull] JObject user) => user.Value("name"); + + /// + /// Gets the user's link. + /// + public static string GetLink([NotNull] JObject user) => user.Value("link"); + + /// + /// Gets the Facebook username. + /// + public static string GetUserName([NotNull] JObject user) => user.Value("username"); + + /// + /// Gets the Facebook email. + /// + public static string GetEmail([NotNull] JObject user) => user.Value("email"); + } +} diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs index 4eab19ddb..1184f4479 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationMiddleware.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Authentication.Facebook /// /// An ASP.NET middleware for authenticating users using Facebook. /// - public class FacebookAuthenticationMiddleware : OAuthAuthenticationMiddleware + public class FacebookAuthenticationMiddleware : OAuthAuthenticationMiddleware { /// /// Initializes a new . @@ -43,11 +43,6 @@ public FacebookAuthenticationMiddleware( { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AppSecret))); } - - if (Options.Notifications == null) - { - Options.Notifications = new FacebookAuthenticationNotifications(); - } } /// diff --git a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationOptions.cs index c7ba48aab..4a5e1ce91 100644 --- a/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.Facebook/FacebookAuthenticationOptions.cs @@ -1,6 +1,7 @@ // 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.Security.Claims; using Microsoft.AspNet.Http; using Microsoft.AspNet.Authentication.OAuth; @@ -9,7 +10,7 @@ namespace Microsoft.AspNet.Authentication.Facebook /// /// Configuration options for . /// - public class FacebookAuthenticationOptions : OAuthAuthenticationOptions + public class FacebookAuthenticationOptions : OAuthAuthenticationOptions { /// /// Initializes a new . @@ -23,6 +24,7 @@ public FacebookAuthenticationOptions() AuthorizationEndpoint = FacebookAuthenticationDefaults.AuthorizationEndpoint; TokenEndpoint = FacebookAuthenticationDefaults.TokenEndpoint; UserInformationEndpoint = FacebookAuthenticationDefaults.UserInformationEndpoint; + SaveTokensAsClaims = false; } // Facebook uses a non-standard term for this field. diff --git a/src/Microsoft.AspNet.Authentication.Facebook/Notifications/FacebookAuthenticatedContext.cs b/src/Microsoft.AspNet.Authentication.Facebook/Notifications/FacebookAuthenticatedContext.cs deleted file mode 100644 index 321b78f8b..000000000 --- a/src/Microsoft.AspNet.Authentication.Facebook/Notifications/FacebookAuthenticatedContext.cs +++ /dev/null @@ -1,62 +0,0 @@ -// 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 Microsoft.AspNet.Authentication.OAuth; -using Newtonsoft.Json.Linq; - -namespace Microsoft.AspNet.Authentication.Facebook -{ - /// - /// Contains information about the login session as well as the user . - /// - public class FacebookAuthenticatedContext : OAuthAuthenticatedContext - { - /// - /// Initializes a new . - /// - /// The HTTP environment. - /// The JSON-serialized user. - /// The Facebook Access token. - public FacebookAuthenticatedContext(HttpContext context, OAuthAuthenticationOptions options, JObject user, TokenResponse tokens) - : base(context, options, user, tokens) - { - Id = TryGetValue(user, "id"); - Name = TryGetValue(user, "name"); - Link = TryGetValue(user, "link"); - UserName = TryGetValue(user, "username"); - Email = TryGetValue(user, "email"); - } - - /// - /// Gets the Facebook user ID. - /// - public string Id { get; private set; } - - /// - /// Gets the user's name. - /// - public string Name { get; private set; } - - /// - /// Gets the user's link. - /// - public string Link { get; private set; } - - /// - /// Gets the Facebook username. - /// - public string UserName { get; private set; } - - /// - /// Gets the Facebook email. - /// - public string Email { get; private set; } - - private static string TryGetValue(JObject user, string propertyName) - { - JToken value; - return user.TryGetValue(propertyName, out value) ? value.ToString() : null; - } - } -} diff --git a/src/Microsoft.AspNet.Authentication.Facebook/Notifications/FacebookAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.Facebook/Notifications/FacebookAuthenticationNotifications.cs deleted file mode 100644 index acee5b54e..000000000 --- a/src/Microsoft.AspNet.Authentication.Facebook/Notifications/FacebookAuthenticationNotifications.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Authentication.OAuth; - -namespace Microsoft.AspNet.Authentication.Facebook -{ - /// - /// The default implementation. - /// - public class FacebookAuthenticationNotifications : OAuthAuthenticationNotifications, IFacebookAuthenticationNotifications - { - /// - /// Initializes a new . - /// - public FacebookAuthenticationNotifications() - { - OnAuthenticated = context => Task.FromResult(null); - } - - /// - /// Gets or sets the function that is invoked when the Authenticated method is invoked. - /// - public Func OnAuthenticated { get; set; } - - /// - /// Invoked whenever Facebook succesfully authenticates a user. - /// - /// Contains information about the login session as well as the user . - /// A representing the completed operation. - public virtual Task Authenticated(FacebookAuthenticatedContext context) - { - return OnAuthenticated(context); - } - } -} diff --git a/src/Microsoft.AspNet.Authentication.Facebook/Notifications/IFacebookAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.Facebook/Notifications/IFacebookAuthenticationNotifications.cs deleted file mode 100644 index aff7de6ee..000000000 --- a/src/Microsoft.AspNet.Authentication.Facebook/Notifications/IFacebookAuthenticationNotifications.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Authentication.OAuth; - -namespace Microsoft.AspNet.Authentication.Facebook -{ - /// - /// Specifies callback methods which the invokes to enable developer control over the authentication process. - /// - public interface IFacebookAuthenticationNotifications : IOAuthAuthenticationNotifications - { - /// - /// Invoked when Facebook succesfully authenticates a user. - /// - /// Contains information about the login session as well as the user . - /// A representing the completed operation. - Task Authenticated(FacebookAuthenticatedContext context); - } -} diff --git a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHandler.cs index 621e77d83..986338fd1 100644 --- a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHandler.cs @@ -14,65 +14,69 @@ namespace Microsoft.AspNet.Authentication.Google { - internal class GoogleAuthenticationHandler : OAuthAuthenticationHandler + internal class GoogleAuthenticationHandler : OAuthAuthenticationHandler { public GoogleAuthenticationHandler(HttpClient httpClient) : base(httpClient) { } - protected override async Task GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens) + protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { // Get the Google user var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); - var graphResponse = await Backchannel.SendAsync(request, Context.RequestAborted); - graphResponse.EnsureSuccessStatusCode(); - var text = await graphResponse.Content.ReadAsStringAsync(); - var user = JObject.Parse(text); - - var context = new GoogleAuthenticatedContext(Context, Options, user, tokens); - var identity = new ClaimsIdentity( - Options.ClaimsIssuer, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - - if (!string.IsNullOrEmpty(context.Id)) + + var response = await Backchannel.SendAsync(request, Context.RequestAborted); + response.EnsureSuccessStatusCode(); + + var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); + + var notification = new OAuthAuthenticatedContext(Context, Options, Backchannel, tokens, payload) { - identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, context.Id, - ClaimValueTypes.String, Options.ClaimsIssuer)); + Properties = properties, + Principal = new ClaimsPrincipal(identity) + }; + + var identifier = GoogleAuthenticationHelper.GetId(payload); + if (!string.IsNullOrEmpty(identifier)) + { + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.GivenName)) + + var givenName = GoogleAuthenticationHelper.GetGivenName(payload); + if (!string.IsNullOrEmpty(givenName)) { - identity.AddClaim(new Claim(ClaimTypes.GivenName, context.GivenName, - ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.GivenName, givenName, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.FamilyName)) + + var familyName = GoogleAuthenticationHelper.GetFamilyName(payload); + if (!string.IsNullOrEmpty(familyName)) { - identity.AddClaim(new Claim(ClaimTypes.Surname, context.FamilyName, - ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.Surname, familyName, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.Name)) + + var name = GoogleAuthenticationHelper.GetName(payload); + if (!string.IsNullOrEmpty(name)) { - identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, ClaimValueTypes.String, - Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.Email)) + + var email = GoogleAuthenticationHelper.GetEmail(payload); + if (!string.IsNullOrEmpty(email)) { - identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, ClaimValueTypes.String, - Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer)); } - if (!string.IsNullOrEmpty(context.Profile)) + + var profile = GoogleAuthenticationHelper.GetProfile(payload); + if (!string.IsNullOrEmpty(profile)) { - identity.AddClaim(new Claim("urn:google:profile", context.Profile, ClaimValueTypes.String, - Options.ClaimsIssuer)); + identity.AddClaim(new Claim("urn:google:profile", profile, ClaimValueTypes.String, Options.ClaimsIssuer)); } - context.Properties = properties; - context.Principal = new ClaimsPrincipal(identity); - await Options.Notifications.Authenticated(context); + await Options.Notifications.Authenticated(notification); - return new AuthenticationTicket(context.Principal, context.Properties, context.Options.AuthenticationScheme); + return new AuthenticationTicket(notification.Principal, notification.Properties, notification.Options.AuthenticationScheme); } // TODO: Abstract this properties override pattern into the base class? diff --git a/src/Microsoft.AspNet.Authentication.Google/Notifications/GoogleAuthenticatedContext.cs b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHelper.cs similarity index 54% rename from src/Microsoft.AspNet.Authentication.Google/Notifications/GoogleAuthenticatedContext.cs rename to src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHelper.cs index 915b80436..611bfffa7 100644 --- a/src/Microsoft.AspNet.Authentication.Google/Notifications/GoogleAuthenticatedContext.cs +++ b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationHelper.cs @@ -1,74 +1,46 @@ // 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; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Authentication; -using Microsoft.AspNet.Authentication.OAuth; +using Microsoft.Framework.Internal; using Newtonsoft.Json.Linq; namespace Microsoft.AspNet.Authentication.Google { /// - /// Contains information about the login session as well as the user . + /// Contains static methods that allow to extract user's information from a + /// instance retrieved from Google after a successful authentication process. /// - public class GoogleAuthenticatedContext : OAuthAuthenticatedContext + public static class GoogleAuthenticationHelper { - /// - /// Initializes a new . - /// - /// The HTTP environment. - /// The JSON-serialized Google user info. - /// Google OAuth 2.0 access token, refresh token, etc. - public GoogleAuthenticatedContext(HttpContext context, OAuthAuthenticationOptions options, JObject user, TokenResponse tokens) - : base(context, options, user, tokens) - { - Id = TryGetValue(user, "id"); - Name = TryGetValue(user, "displayName"); - GivenName = TryGetValue(user, "name", "givenName"); - FamilyName = TryGetValue(user, "name", "familyName"); - Profile = TryGetValue(user, "url"); - Email = TryGetFirstValue(user, "emails", "value"); - } - /// /// Gets the Google user ID. /// - public string Id { get; private set; } + public static string GetId([NotNull] JObject user) => user.Value("id"); /// /// Gets the user's name. /// - public string Name { get; private set; } + public static string GetName([NotNull] JObject user) => user.Value("displayName"); /// /// Gets the user's given name. /// - public string GivenName { get; set; } + public static string GetGivenName([NotNull] JObject user) => TryGetValue(user, "name", "givenName"); /// /// Gets the user's family name. /// - public string FamilyName { get; set; } + public static string GetFamilyName([NotNull] JObject user) => TryGetValue(user, "name", "familyName"); /// /// Gets the user's profile link. /// - public string Profile { get; private set; } + public static string GetProfile([NotNull] JObject user) => user.Value("url"); /// /// Gets the user's email. /// - public string Email { get; private set; } - - private static string TryGetValue(JObject user, string propertyName) - { - JToken value; - return user.TryGetValue(propertyName, out value) ? value.ToString() : null; - } + public static string GetEmail([NotNull] JObject user) => TryGetFirstValue(user, "emails", "value"); // Get the given subProperty from a property. private static string TryGetValue(JObject user, string propertyName, string subProperty) diff --git a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs index db7a3c988..3f07bc0e9 100644 --- a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationMiddleware.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Authentication.Google /// An ASP.NET middleware for authenticating users using Google OAuth 2.0. /// [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Middleware are not disposable.")] - public class GoogleAuthenticationMiddleware : OAuthAuthenticationMiddleware + public class GoogleAuthenticationMiddleware : OAuthAuthenticationMiddleware { /// /// Initializes a new . @@ -35,11 +35,6 @@ public GoogleAuthenticationMiddleware( ConfigureOptions configureOptions = null) : base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options, configureOptions) { - if (Options.Notifications == null) - { - Options.Notifications = new GoogleAuthenticationNotifications(); - } - if (Options.Scope.Count == 0) { // Google OAuth 2.0 asks for non-empty scope. If user didn't set it, set default scope to diff --git a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationOptions.cs index e65b800ed..f0c5d0f22 100644 --- a/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.Google/GoogleAuthenticationOptions.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.Google /// /// Configuration options for . /// - public class GoogleAuthenticationOptions : OAuthAuthenticationOptions + public class GoogleAuthenticationOptions : OAuthAuthenticationOptions { /// /// Initializes a new . @@ -22,6 +22,7 @@ public GoogleAuthenticationOptions() AuthorizationEndpoint = GoogleAuthenticationDefaults.AuthorizationEndpoint; TokenEndpoint = GoogleAuthenticationDefaults.TokenEndpoint; UserInformationEndpoint = GoogleAuthenticationDefaults.UserInformationEndpoint; + SaveTokensAsClaims = false; } /// diff --git a/src/Microsoft.AspNet.Authentication.Google/Notifications/GoogleAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.Google/Notifications/GoogleAuthenticationNotifications.cs deleted file mode 100644 index 51269aeff..000000000 --- a/src/Microsoft.AspNet.Authentication.Google/Notifications/GoogleAuthenticationNotifications.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Authentication.OAuth; - -namespace Microsoft.AspNet.Authentication.Google -{ - /// - /// The default implementation. - /// - public class GoogleAuthenticationNotifications : OAuthAuthenticationNotifications, IGoogleAuthenticationNotifications - { - /// - /// Initializes a new . - /// - public GoogleAuthenticationNotifications() - { - OnAuthenticated = context => Task.FromResult(null); - } - - /// - /// Gets or sets the function that is invoked when the Authenticated method is invoked. - /// - public Func OnAuthenticated { get; set; } - - /// - /// Invoked whenever Google succesfully authenticates a user. - /// - /// Contains information about the login session as well as the user . - /// A representing the completed operation. - public virtual Task Authenticated(GoogleAuthenticatedContext context) - { - return OnAuthenticated(context); - } - } -} diff --git a/src/Microsoft.AspNet.Authentication.Google/Notifications/IGoogleAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.Google/Notifications/IGoogleAuthenticationNotifications.cs deleted file mode 100644 index 30ce69778..000000000 --- a/src/Microsoft.AspNet.Authentication.Google/Notifications/IGoogleAuthenticationNotifications.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Authentication.OAuth; - -namespace Microsoft.AspNet.Authentication.Google -{ - /// - /// Specifies callback methods which the invokes to enable developer control over the authentication process. - /// - public interface IGoogleAuthenticationNotifications : IOAuthAuthenticationNotifications - { - /// - /// Invoked whenever Google succesfully authenticates a user. - /// - /// Contains information about the login session as well as the user . - /// A representing the completed operation. - Task Authenticated(GoogleAuthenticatedContext context); - } -} diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs index 31484f998..0fac563eb 100644 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHandler.cs @@ -11,45 +11,52 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount { - internal class MicrosoftAccountAuthenticationHandler : OAuthAuthenticationHandler + internal class MicrosoftAccountAuthenticationHandler : OAuthAuthenticationHandler { public MicrosoftAccountAuthenticationHandler(HttpClient httpClient) : base(httpClient) { } - protected override async Task GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens) + protected override async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { var request = new HttpRequestMessage(HttpMethod.Get, Options.UserInformationEndpoint); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokens.AccessToken); - var graphResponse = await Backchannel.SendAsync(request, Context.RequestAborted); - graphResponse.EnsureSuccessStatusCode(); - var accountString = await graphResponse.Content.ReadAsStringAsync(); - var accountInformation = JObject.Parse(accountString); - - var context = new MicrosoftAccountAuthenticatedContext(Context, Options, accountInformation, tokens); - context.Properties = properties; - var identity = new ClaimsIdentity( - new[] - { - new Claim(ClaimTypes.NameIdentifier, context.Id, ClaimValueTypes.String, Options.ClaimsIssuer), - new Claim(ClaimTypes.Name, context.Name, ClaimValueTypes.String, Options.ClaimsIssuer), - new Claim("urn:microsoftaccount:id", context.Id, ClaimValueTypes.String, Options.ClaimsIssuer), - new Claim("urn:microsoftaccount:name", context.Name, ClaimValueTypes.String, Options.ClaimsIssuer) - }, - Options.ClaimsIssuer, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - - if (!string.IsNullOrWhiteSpace(context.Email)) + + var response = await Backchannel.SendAsync(request, Context.RequestAborted); + response.EnsureSuccessStatusCode(); + + var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); + + var notification = new OAuthAuthenticatedContext(Context, Options, Backchannel, tokens, payload) + { + Properties = properties, + Principal = new ClaimsPrincipal(identity) + }; + + var identifier = MicrosoftAccountAuthenticationHelper.GetId(payload); + if (!string.IsNullOrEmpty(identifier)) + { + identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim("urn:microsoftaccount:id", identifier, ClaimValueTypes.String, Options.ClaimsIssuer)); + } + + var name = MicrosoftAccountAuthenticationHelper.GetName(payload); + if (!string.IsNullOrEmpty(name)) + { + identity.AddClaim(new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim("urn:microsoftaccount:name", name, ClaimValueTypes.String, Options.ClaimsIssuer)); + } + + var email = MicrosoftAccountAuthenticationHelper.GetEmail(payload); + if (!string.IsNullOrWhiteSpace(email)) { - identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, ClaimValueTypes.String, Options.ClaimsIssuer)); + identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer)); } - context.Principal = new ClaimsPrincipal(identity); - await Options.Notifications.Authenticated(context); + await Options.Notifications.Authenticated(notification); - return new AuthenticationTicket(context.Principal, context.Properties, context.Options.AuthenticationScheme); + return new AuthenticationTicket(notification.Principal, notification.Properties, notification.Options.AuthenticationScheme); } } } diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHelper.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHelper.cs new file mode 100644 index 000000000..e2629cdf3 --- /dev/null +++ b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationHelper.cs @@ -0,0 +1,41 @@ +// 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.Framework.Internal; +using Newtonsoft.Json.Linq; + +namespace Microsoft.AspNet.Authentication.MicrosoftAccount +{ + /// + /// Contains static methods that allow to extract user's information from a + /// instance retrieved from Google after a successful authentication process. + /// + public static class MicrosoftAccountAuthenticationHelper + { + /// + /// Gets the Microsoft Account user ID. + /// + public static string GetId([NotNull] JObject user) => user.Value("id"); + + /// + /// Gets the user's name. + /// + public static string GetName([NotNull] JObject user) => user.Value("name"); + + /// + /// Gets the user's first name. + /// + public static string GetFirstName([NotNull] JObject user) => user.Value("first_name"); + + /// + /// Gets the user's last name. + /// + public static string GetLastName([NotNull] JObject user) => user.Value("last_name"); + + /// + /// Gets the user's email address. + /// + public static string GetEmail([NotNull] JObject user) => user.Value("emails") + ?.Value("preferred"); + } +} diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs index 362664629..e4f575170 100644 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationMiddleware.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount /// /// An ASP.NET middleware for authenticating users using the Microsoft Account service. /// - public class MicrosoftAccountAuthenticationMiddleware : OAuthAuthenticationMiddleware + public class MicrosoftAccountAuthenticationMiddleware : OAuthAuthenticationMiddleware { /// /// Initializes a new . @@ -33,10 +33,6 @@ public MicrosoftAccountAuthenticationMiddleware( ConfigureOptions configureOptions = null) : base(next, dataProtectionProvider, loggerFactory, encoder, externalOptions, options, configureOptions) { - if (Options.Notifications == null) - { - Options.Notifications = new MicrosoftAccountAuthenticationNotifications(); - } if (Options.Scope.Count == 0) { // LiveID requires a scope string, so if the user didn't set one we go for the least possible. diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationOptions.cs index 0212b6e7d..04d2bfa33 100644 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/MicrosoftAccountAuthenticationOptions.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount /// /// Configuration options for . /// - public class MicrosoftAccountAuthenticationOptions : OAuthAuthenticationOptions + public class MicrosoftAccountAuthenticationOptions : OAuthAuthenticationOptions { /// /// Initializes a new . @@ -22,6 +22,7 @@ public MicrosoftAccountAuthenticationOptions() AuthorizationEndpoint = MicrosoftAccountAuthenticationDefaults.AuthorizationEndpoint; TokenEndpoint = MicrosoftAccountAuthenticationDefaults.TokenEndpoint; UserInformationEndpoint = MicrosoftAccountAuthenticationDefaults.UserInformationEndpoint; + SaveTokensAsClaims = false; } } } diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/IMicrosoftAccountAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/IMicrosoftAccountAuthenticationNotifications.cs deleted file mode 100644 index d9647e4cd..000000000 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/IMicrosoftAccountAuthenticationNotifications.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Authentication.OAuth; - -namespace Microsoft.AspNet.Authentication.MicrosoftAccount -{ - /// - /// Specifies callback methods which the invokes to enable developer control over the authentication process. - /// - public interface IMicrosoftAccountAuthenticationNotifications : IOAuthAuthenticationNotifications - { - /// - /// Invoked whenever Microsoft succesfully authenticates a user. - /// - /// Contains information about the login session as well as the user . - /// A representing the completed operation. - Task Authenticated(MicrosoftAccountAuthenticatedContext context); - } -} diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/MicrosoftAccountAuthenticatedContext.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/MicrosoftAccountAuthenticatedContext.cs deleted file mode 100644 index c5bfa3f9b..000000000 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/MicrosoftAccountAuthenticatedContext.cs +++ /dev/null @@ -1,81 +0,0 @@ -// 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.Collections.Generic; -using System.Linq; -using Microsoft.AspNet.Authentication.OAuth; -using Microsoft.AspNet.Http; -using Microsoft.Framework.Internal; -using Newtonsoft.Json.Linq; - -namespace Microsoft.AspNet.Authentication.MicrosoftAccount -{ - /// - /// Contains information about the login session as well as the user . - /// - public class MicrosoftAccountAuthenticatedContext : OAuthAuthenticatedContext - { - /// - /// Initializes a new . - /// - /// The HTTP environment. - /// The JSON-serialized user. - /// The access token provided by the Microsoft authentication service. - public MicrosoftAccountAuthenticatedContext(HttpContext context, OAuthAuthenticationOptions options, [NotNull] JObject user, TokenResponse tokens) - : base(context, options, user, tokens) - { - IDictionary userAsDictionary = user; - - JToken userId = User["id"]; - if (userId == null) - { - throw new ArgumentException(Resources.Exception_MissingId, nameof(user)); - } - - Id = userId.ToString(); - Name = PropertyValueIfExists("name", userAsDictionary); - FirstName = PropertyValueIfExists("first_name", userAsDictionary); - LastName = PropertyValueIfExists("last_name", userAsDictionary); - - if (userAsDictionary.ContainsKey("emails")) - { - JToken emailsNode = user["emails"]; - foreach (var childAsProperty in emailsNode.OfType().Where(childAsProperty => childAsProperty.Name == "preferred")) - { - Email = childAsProperty.Value.ToString(); - } - } - } - - /// - /// Gets the Microsoft Account user ID. - /// - public string Id { get; private set; } - - /// - /// Gets the user's name. - /// - public string Name { get; private set; } - - /// - /// Gets the user's first name. - /// - public string FirstName { get; private set; } - - /// - /// Gets the user's last name. - /// - public string LastName { get; private set; } - - /// - /// Gets the user's email address. - /// - public string Email { get; private set; } - - private static string PropertyValueIfExists(string property, IDictionary dictionary) - { - return dictionary.ContainsKey(property) ? dictionary[property].ToString() : null; - } - } -} diff --git a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/MicrosoftAccountAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/MicrosoftAccountAuthenticationNotifications.cs deleted file mode 100644 index 16491a4ed..000000000 --- a/src/Microsoft.AspNet.Authentication.MicrosoftAccount/Notifications/MicrosoftAccountAuthenticationNotifications.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNet.Authentication.OAuth; - -namespace Microsoft.AspNet.Authentication.MicrosoftAccount -{ - /// - /// Default implementation. - /// - public class MicrosoftAccountAuthenticationNotifications : OAuthAuthenticationNotifications, IMicrosoftAccountAuthenticationNotifications - { - /// - /// Initializes a new - /// - public MicrosoftAccountAuthenticationNotifications() - { - OnAuthenticated = context => Task.FromResult(0); - } - - /// - /// Gets or sets the function that is invoked when the Authenticated method is invoked. - /// - public Func OnAuthenticated { get; set; } - - /// - /// Invoked whenever Microsoft succesfully authenticates a user - /// - /// Contains information about the login session as well as the user . - /// A representing the completed operation. - public virtual Task Authenticated(MicrosoftAccountAuthenticatedContext context) - { - return OnAuthenticated(context); - } - } -} diff --git a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/IOAuthAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/IOAuthAuthenticationNotifications.cs index 6d386d089..522f1cb16 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/IOAuthAuthenticationNotifications.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/IOAuthAuthenticationNotifications.cs @@ -1,7 +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 System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNet.Http.Authentication; namespace Microsoft.AspNet.Authentication.OAuth { @@ -12,21 +14,21 @@ public interface IOAuthAuthenticationNotifications { /// /// Invoked after the provider successfully authenticates a user. This can be used to retrieve user information. - /// This notification may not be invoked by sub-classes of OAuthAuthenticationHandler if they override GetUserInformationAsync. + /// This notification may not be invoked by sub-classes of OAuthAuthenticationHandler if they override CreateTicketAsync. /// /// Contains information about the login session. /// A representing the completed operation. - Task GetUserInformationAsync(OAuthGetUserInformationContext context); + Task Authenticated(OAuthAuthenticatedContext context); /// - /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. /// /// /// A representing the completed operation. Task ReturnEndpoint(OAuthReturnEndpointContext context); /// - /// Called when a Challenge causes a redirect to authorize endpoint in the Microsoft middleware. + /// Called when a Challenge causes a redirect to the authorize endpoint. /// /// Contains redirect URI and of the challenge. void ApplyRedirect(OAuthApplyRedirectContext context); diff --git a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticatedContext.cs b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticatedContext.cs index d03411747..e59f04d07 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticatedContext.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticatedContext.cs @@ -3,10 +3,12 @@ using System; using System.Globalization; +using System.Net.Http; using System.Security.Claims; +using Microsoft.AspNet.Authentication.Notifications; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Authentication; -using Microsoft.AspNet.Authentication.Notifications; +using Microsoft.Framework.Internal; using Newtonsoft.Json.Linq; namespace Microsoft.AspNet.Authentication.OAuth @@ -20,54 +22,97 @@ public class OAuthAuthenticatedContext : BaseContext /// Initializes a new . /// /// The HTTP environment. - /// The JSON-serialized user. + /// The options used by the authentication middleware. + /// The HTTP client used by the authentication middleware /// The tokens returned from the token endpoint. - public OAuthAuthenticatedContext(HttpContext context, OAuthAuthenticationOptions options, JObject user, - TokenResponse tokens) - : base(context, options) + public OAuthAuthenticatedContext( + [NotNull] HttpContext context, + [NotNull] OAuthAuthenticationOptions options, + [NotNull] HttpClient backchannel, + [NotNull] OAuthTokenResponse tokens) + : this(context, options, backchannel, tokens, user: new JObject()) { - User = user; - AccessToken = tokens.AccessToken; - TokenType = tokens.TokenType; - RefreshToken = tokens.RefreshToken; + } - int expiresInValue; - if (Int32.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresInValue)) - { - ExpiresIn = TimeSpan.FromSeconds(expiresInValue); - } + /// + /// Initializes a new . + /// + /// The HTTP environment. + /// The options used by the authentication middleware. + /// The HTTP client used by the authentication middleware + /// The tokens returned from the token endpoint. + /// The JSON-serialized user. + public OAuthAuthenticatedContext( + [NotNull] HttpContext context, + [NotNull] OAuthAuthenticationOptions options, + [NotNull] HttpClient backchannel, + [NotNull] OAuthTokenResponse tokens, + [NotNull] JObject user) + : base(context, options) + { + TokenResponse = tokens; + Backchannel = backchannel; } /// - /// Gets the JSON-serialized user. + /// Gets the JSON-serialized user or an empty + /// if it is not available. + /// + public JObject User { get; } + + /// + /// Gets the token response returned by the authentication service. /// - public JObject User { get; protected set; } + public OAuthTokenResponse TokenResponse { get; } /// /// Gets the access token provided by the authentication service. /// - public string AccessToken { get; protected set; } + public string AccessToken => TokenResponse.AccessToken; /// /// Gets the access token type provided by the authentication service. /// - public string TokenType { get; protected set; } + public string TokenType => TokenResponse.TokenType; /// /// Gets the refresh token provided by the authentication service. /// - public string RefreshToken { get; protected set; } + public string RefreshToken => TokenResponse.RefreshToken; /// /// Gets the access token expiration time. /// - public TimeSpan? ExpiresIn { get; protected set; } + public TimeSpan? ExpiresIn + { + get + { + int value; + if (int.TryParse(TokenResponse.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) + { + return TimeSpan.FromSeconds(value); + } + + return null; + } + } + + /// + /// Gets the backchannel used to communicate with the provider. + /// + public HttpClient Backchannel { get; } /// - /// Gets the representing the user. + /// Gets the representing the user. /// public ClaimsPrincipal Principal { get; set; } + /// + /// Gets the main identity exposed by . + /// This property returns null when is null. + /// + public ClaimsIdentity Identity => Principal?.Identity as ClaimsIdentity; + /// /// Gets or sets a property bag for common authentication properties. /// diff --git a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticationNotifications.cs b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticationNotifications.cs index aa6d7a811..9716b1401 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticationNotifications.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthAuthenticationNotifications.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Security.Claims; using System.Threading.Tasks; +using Microsoft.AspNet.Http.Authentication; namespace Microsoft.AspNet.Authentication.OAuth { @@ -11,58 +13,39 @@ namespace Microsoft.AspNet.Authentication.OAuth /// public class OAuthAuthenticationNotifications : IOAuthAuthenticationNotifications { - /// - /// Initializes a new - /// - public OAuthAuthenticationNotifications() - { - OnGetUserInformationAsync = OAuthAuthenticationDefaults.DefaultOnGetUserInformationAsync; - OnReturnEndpoint = context => Task.FromResult(0); - OnApplyRedirect = context => context.Response.Redirect(context.RedirectUri); - } - /// /// Gets or sets the function that is invoked when the Authenticated method is invoked. /// - public Func OnGetUserInformationAsync { get; set; } + public Func OnAuthenticated { get; set; } = context => Task.FromResult(0); /// /// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked. /// - public Func OnReturnEndpoint { get; set; } + public Func OnReturnEndpoint { get; set; } = context => Task.FromResult(0); /// /// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked. /// - public Action OnApplyRedirect { get; set; } + public Action OnApplyRedirect { get; set; } = context => context.Response.Redirect(context.RedirectUri); /// /// Invoked after the provider successfully authenticates a user. /// - /// Contains information about the login session as well as the user . + /// Contains information about the login session as well as the user . /// A representing the completed operation. - public virtual Task GetUserInformationAsync(OAuthGetUserInformationContext context) - { - return OnGetUserInformationAsync(context); - } + public virtual Task Authenticated(OAuthAuthenticatedContext context) => OnAuthenticated(context); /// - /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. + /// Invoked prior to the being saved in a local cookie and the browser being redirected to the originally requested URL. /// - /// Contains information about the login session as well as the user + /// Contains information about the login session as well as the user /// A representing the completed operation. - public virtual Task ReturnEndpoint(OAuthReturnEndpointContext context) - { - return OnReturnEndpoint(context); - } + public virtual Task ReturnEndpoint(OAuthReturnEndpointContext context) => OnReturnEndpoint(context); /// /// Called when a Challenge causes a redirect to authorize endpoint in the OAuth middleware. /// /// Contains redirect URI and of the challenge. - public virtual void ApplyRedirect(OAuthApplyRedirectContext context) - { - OnApplyRedirect(context); - } + public virtual void ApplyRedirect(OAuthApplyRedirectContext context) => OnApplyRedirect(context); } } diff --git a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthGetUserInformationContext.cs b/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthGetUserInformationContext.cs deleted file mode 100644 index 74d02cd58..000000000 --- a/src/Microsoft.AspNet.Authentication.OAuth/Notifications/OAuthGetUserInformationContext.cs +++ /dev/null @@ -1,75 +0,0 @@ -// 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; -using Microsoft.AspNet.Http; -using Microsoft.AspNet.Http.Authentication; -using Microsoft.AspNet.Authentication.Notifications; - -namespace Microsoft.AspNet.Authentication.OAuth -{ - /// - /// Contains information about the login session as well as the user . - /// - public class OAuthGetUserInformationContext : BaseContext - { - /// - /// Initializes a new . - /// - /// The HTTP environment. - /// The JSON-serialized user. - /// The tokens returned from the token endpoint. - public OAuthGetUserInformationContext(HttpContext context, OAuthAuthenticationOptions options, HttpClient backchannel, TokenResponse tokens) - : base(context, options) - { - AccessToken = tokens.AccessToken; - TokenType = tokens.TokenType; - RefreshToken = tokens.RefreshToken; - Backchannel = backchannel; - - int expiresInValue; - if (Int32.TryParse(tokens.ExpiresIn, NumberStyles.Integer, CultureInfo.InvariantCulture, out expiresInValue)) - { - ExpiresIn = TimeSpan.FromSeconds(expiresInValue); - } - } - - /// - /// Gets the access token provided by the authentication service. - /// - public string AccessToken { get; protected set; } - - /// - /// Gets the access token type provided by the authentication service. - /// - public string TokenType { get; protected set; } - - /// - /// Gets the refresh token provided by the authentication service. - /// - public string RefreshToken { get; protected set; } - - /// - /// Gets the access token expiration time. - /// - public TimeSpan? ExpiresIn { get; protected set; } - - /// - /// Gets the backchannel used to communicate with the provider. - /// - public HttpClient Backchannel { get; protected set; } - - /// - /// Gets the representing the user. - /// - public ClaimsPrincipal Principal { get; set; } - - /// - /// Gets or sets a property bag for common authentication properties. - /// - public AuthenticationProperties Properties { get; set; } - } -} diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationDefaults.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationDefaults.cs deleted file mode 100644 index a9b93d43a..000000000 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationDefaults.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Security.Claims; -using System.Threading.Tasks; - -namespace Microsoft.AspNet.Authentication.OAuth -{ - public static class OAuthAuthenticationDefaults - { - public static readonly Func DefaultOnGetUserInformationAsync = context => - { - // If the developer doesn't specify a user-info callback, just give them the tokens. - var identity = new ClaimsIdentity( - context.Options.AuthenticationScheme, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType); - - identity.AddClaim(new Claim("access_token", context.AccessToken, ClaimValueTypes.String, context.Options.AuthenticationScheme)); - if (!string.IsNullOrEmpty(context.RefreshToken)) - { - identity.AddClaim(new Claim("refresh_token", context.RefreshToken, ClaimValueTypes.String, context.Options.AuthenticationScheme)); - } - if (!string.IsNullOrEmpty(context.TokenType)) - { - identity.AddClaim(new Claim("token_type", context.TokenType, ClaimValueTypes.String, context.Options.AuthenticationScheme)); - } - if (context.ExpiresIn.HasValue) - { - identity.AddClaim(new Claim("expires_in", context.ExpiresIn.Value.TotalSeconds.ToString(CultureInfo.InvariantCulture), - ClaimValueTypes.String, context.Options.AuthenticationScheme)); - } - context.Principal = new ClaimsPrincipal(identity); - return Task.FromResult(0); - }; - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationExtensions.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationExtensions.cs index 3c6b3db9a..fec82a735 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationExtensions.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationExtensions.cs @@ -19,10 +19,10 @@ public static class OAuthAuthenticationExtensions /// The passed to the configure method. /// The middleware configuration options. /// The updated . - public static IApplicationBuilder UseOAuthAuthentication([NotNull] this IApplicationBuilder app, [NotNull] string authenticationScheme, Action> configureOptions = null) + public static IApplicationBuilder UseOAuthAuthentication([NotNull] this IApplicationBuilder app, [NotNull] string authenticationScheme, Action configureOptions = null) { - return app.UseMiddleware, IOAuthAuthenticationNotifications>>( - new ConfigureOptions>(options => + return app.UseMiddleware>( + new ConfigureOptions(options => { options.AuthenticationScheme = authenticationScheme; options.Caption = authenticationScheme; @@ -30,10 +30,6 @@ public static IApplicationBuilder UseOAuthAuthentication([NotNull] this IApplica { configureOptions(options); } - if (options.Notifications == null) - { - options.Notifications = new OAuthAuthenticationNotifications(); - } }) { Name = authenticationScheme, diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs index 1be0ccff8..2741ca4d5 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationHandler.cs @@ -6,6 +6,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Security.Cryptography; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Authentication.DataHandler.Encoder; using Microsoft.AspNet.Http; @@ -19,9 +20,7 @@ namespace Microsoft.AspNet.Authentication.OAuth { - public class OAuthAuthenticationHandler : AuthenticationHandler - where TOptions : OAuthAuthenticationOptions - where TNotifications : IOAuthAuthenticationNotifications + public class OAuthAuthenticationHandler : AuthenticationHandler where TOptions : OAuthAuthenticationOptions { private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create(); @@ -124,7 +123,33 @@ public override async Task AuthenticateAsync() return new AuthenticationTicket(properties, Options.AuthenticationScheme); } - return await GetUserInformationAsync(properties, tokens); + var identity = new ClaimsIdentity(Options.ClaimsIssuer); + + if (Options.SaveTokensAsClaims) + { + identity.AddClaim(new Claim("access_token", tokens.AccessToken, + ClaimValueTypes.String, Options.ClaimsIssuer)); + + if (!string.IsNullOrEmpty(tokens.RefreshToken)) + { + identity.AddClaim(new Claim("refresh_token", tokens.RefreshToken, + ClaimValueTypes.String, Options.ClaimsIssuer)); + } + + if (!string.IsNullOrEmpty(tokens.TokenType)) + { + identity.AddClaim(new Claim("token_type", tokens.TokenType, + ClaimValueTypes.String, Options.ClaimsIssuer)); + } + + if (!string.IsNullOrEmpty(tokens.ExpiresIn)) + { + identity.AddClaim(new Claim("expires_in", tokens.ExpiresIn, + ClaimValueTypes.String, Options.ClaimsIssuer)); + } + } + + return await CreateTicketAsync(identity, properties, tokens); } catch (Exception ex) { @@ -133,7 +158,7 @@ public override async Task AuthenticateAsync() } } - protected virtual async Task ExchangeCodeAsync(string code, string redirectUri) + protected virtual async Task ExchangeCodeAsync(string code, string redirectUri) { var tokenRequestParameters = new Dictionary() { @@ -151,20 +176,27 @@ protected virtual async Task ExchangeCodeAsync(string code, strin requestMessage.Content = requestContent; var response = await Backchannel.SendAsync(requestMessage, Context.RequestAborted); response.EnsureSuccessStatusCode(); - var oauthTokenResponse = await response.Content.ReadAsStringAsync(); + var payload = JObject.Parse(await response.Content.ReadAsStringAsync()); - var oauth2Token = JObject.Parse(oauthTokenResponse); - return new TokenResponse(oauth2Token); + return new OAuthTokenResponse(payload); } - protected virtual async Task GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens) + protected virtual async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens) { - var context = new OAuthGetUserInformationContext(Context, Options, Backchannel, tokens) + var notification = new OAuthAuthenticatedContext(Context, Options, Backchannel, tokens) { - Properties = properties, + Principal = new ClaimsPrincipal(identity), + Properties = properties }; - await Options.Notifications.GetUserInformationAsync(context); - return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme); + + await Options.Notifications.Authenticated(notification); + + if (notification.Principal?.Identity == null) + { + return null; + } + + return new AuthenticationTicket(notification.Principal, notification.Properties, Options.AuthenticationScheme); } protected override Task HandleUnauthorizedAsync([NotNull] ChallengeContext context) diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs index 67041821c..c1c884810 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationMiddleware.cs @@ -19,9 +19,7 @@ namespace Microsoft.AspNet.Authentication.OAuth /// An ASP.NET middleware for authenticating users using OAuth services. /// [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Middleware are not disposable.")] - public class OAuthAuthenticationMiddleware : AuthenticationMiddleware - where TOptions : OAuthAuthenticationOptions, new() - where TNotifications : IOAuthAuthenticationNotifications + public class OAuthAuthenticationMiddleware : AuthenticationMiddleware where TOptions : OAuthAuthenticationOptions, new() { /// /// Initializes a new . @@ -58,12 +56,12 @@ public OAuthAuthenticationMiddleware( if (string.IsNullOrWhiteSpace(Options.AuthorizationEndpoint)) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "AuthorizationEndpoint")); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AuthorizationEndpoint))); } if (string.IsNullOrWhiteSpace(Options.TokenEndpoint)) { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "TokenEndpoint")); + throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.TokenEndpoint))); } if (Options.StateDataFormat == null) @@ -82,10 +80,6 @@ public OAuthAuthenticationMiddleware( { Options.SignInScheme = externalOptions.Options.SignInScheme; } - if (string.IsNullOrEmpty(Options.SignInScheme)) - { - throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, "SignInScheme")); - } } protected HttpClient Backchannel { get; private set; } @@ -96,11 +90,11 @@ public OAuthAuthenticationMiddleware( /// An configured with the supplied to the constructor. protected override AuthenticationHandler CreateHandler() { - return new OAuthAuthenticationHandler(Backchannel); + return new OAuthAuthenticationHandler(Backchannel); } [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")] - private static HttpMessageHandler ResolveHttpMessageHandler(OAuthAuthenticationOptions options) + private static HttpMessageHandler ResolveHttpMessageHandler(OAuthAuthenticationOptions options) { HttpMessageHandler handler = options.BackchannelHttpHandler ?? #if DNX451 diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs index 62296c4a9..6244de1ef 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Security.Claims; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Authentication; +using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Authentication.OAuth { @@ -14,15 +16,6 @@ namespace Microsoft.AspNet.Authentication.OAuth /// public class OAuthAuthenticationOptions : AuthenticationOptions { - /// - /// Initializes a new . - /// - public OAuthAuthenticationOptions() - { - Scope = new List(); - BackchannelTimeout = TimeSpan.FromSeconds(60); - } - /// /// Gets or sets the provider-assigned client id. /// @@ -46,7 +39,7 @@ public OAuthAuthenticationOptions() /// /// Gets or sets the URI the middleware will access to obtain the user information. /// This value is not used in the default implementation, it is for use in custom implementations of - /// IOAuthAuthenticationNotifications.GetUserInformationAsync or OAuthAuthenticationHandler.GetUserInformationAsync. + /// IOAuthAuthenticationNotifications.Authenticated or OAuthAuthenticationHandler.CreateTicketAsync. /// public string UserInformationEndpoint { get; set; } @@ -62,6 +55,7 @@ public OAuthAuthenticationOptions() /// validating the subject name and if the signing chain is a trusted party. public ICertificateValidator BackchannelCertificateValidator { get; set; } #endif + /// /// Get or sets the text that the user can display on a sign in user interface. /// @@ -77,7 +71,7 @@ public string Caption /// /// The back channel timeout. /// - public TimeSpan BackchannelTimeout { get; set; } + public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromSeconds(60); /// /// The HttpMessageHandler used to communicate with the auth provider. @@ -86,10 +80,15 @@ public string Caption /// public HttpMessageHandler BackchannelHttpHandler { get; set; } + /// + /// Gets or sets the used to handle authentication events. + /// + public IOAuthAuthenticationNotifications Notifications { get; [param: NotNull] set; } = new OAuthAuthenticationNotifications(); + /// /// A list of permissions to request. /// - public IList Scope { get; private set; } + public IList Scope { get; } = new List(); /// /// The request path within the application's base path where the user-agent will be returned. @@ -109,5 +108,13 @@ public string Caption /// Gets or sets the type used to secure data handled by the middleware. /// public ISecureDataFormat StateDataFormat { get; set; } + + /// + /// Defines whether access and refresh tokens should be stored in the + /// after a successful authentication. + /// You can set this property to false to reduce the size of the final + /// authentication cookie. Note that social providers set this property to false by default. + /// + public bool SaveTokensAsClaims { get; set; } = true; } } diff --git a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions`1.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions`1.cs deleted file mode 100644 index aa989dfe5..000000000 --- a/src/Microsoft.AspNet.Authentication.OAuth/OAuthAuthenticationOptions`1.cs +++ /dev/null @@ -1,16 +0,0 @@ -// 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. - -namespace Microsoft.AspNet.Authentication.OAuth -{ - /// - /// Configuration options for . - /// - public class OAuthAuthenticationOptions : OAuthAuthenticationOptions where TNotifications : IOAuthAuthenticationNotifications - { - /// - /// Gets or sets the used to handle authentication events. - /// - public TNotifications Notifications { get; set; } - } -} diff --git a/src/Microsoft.AspNet.Authentication.OAuth/TokenResponse.cs b/src/Microsoft.AspNet.Authentication.OAuth/OAuthTokenResponse.cs similarity index 90% rename from src/Microsoft.AspNet.Authentication.OAuth/TokenResponse.cs rename to src/Microsoft.AspNet.Authentication.OAuth/OAuthTokenResponse.cs index 5bcb80ad8..f79666955 100644 --- a/src/Microsoft.AspNet.Authentication.OAuth/TokenResponse.cs +++ b/src/Microsoft.AspNet.Authentication.OAuth/OAuthTokenResponse.cs @@ -5,9 +5,9 @@ namespace Microsoft.AspNet.Authentication.OAuth { - public class TokenResponse + public class OAuthTokenResponse { - public TokenResponse(JObject response) + public OAuthTokenResponse(JObject response) { Response = response; AccessToken = response.Value("access_token"); diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs index b9dc09c99..6a0bc40f2 100644 --- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs +++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationHandler.cs @@ -82,24 +82,6 @@ public override async Task AuthenticateAsync() return new AuthenticationTicket(properties, Options.AuthenticationScheme); } - var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier); - - var context = new TwitterAuthenticatedContext(Context, accessToken.UserId, accessToken.ScreenName, accessToken.Token, accessToken.TokenSecret); - - context.Principal = new ClaimsPrincipal( - new ClaimsIdentity( - new[] - { - new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.ClaimsIssuer), - new Claim(ClaimTypes.Name, accessToken.ScreenName, "http://www.w3.org/2001/XMLSchema#string", Options.ClaimsIssuer), - new Claim("urn:twitter:userid", accessToken.UserId, "http://www.w3.org/2001/XMLSchema#string", Options.ClaimsIssuer), - new Claim("urn:twitter:screenname", accessToken.ScreenName, "http://www.w3.org/2001/XMLSchema#string", Options.ClaimsIssuer) - }, - Options.ClaimsIssuer, - ClaimsIdentity.DefaultNameClaimType, - ClaimsIdentity.DefaultRoleClaimType)); - context.Properties = requestToken.Properties; - var cookieOptions = new CookieOptions { HttpOnly = true, @@ -108,9 +90,23 @@ public override async Task AuthenticateAsync() Response.Cookies.Delete(StateCookie, cookieOptions); - await Options.Notifications.Authenticated(context); - - return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme); + var accessToken = await ObtainAccessTokenAsync(Options.ConsumerKey, Options.ConsumerSecret, requestToken, oauthVerifier); + + var identity = new ClaimsIdentity(new[] + { + new Claim(ClaimTypes.NameIdentifier, accessToken.UserId, ClaimValueTypes.String, Options.ClaimsIssuer), + new Claim(ClaimTypes.Name, accessToken.ScreenName, ClaimValueTypes.String, Options.ClaimsIssuer), + new Claim("urn:twitter:userid", accessToken.UserId, ClaimValueTypes.String, Options.ClaimsIssuer), + new Claim("urn:twitter:screenname", accessToken.ScreenName, ClaimValueTypes.String, Options.ClaimsIssuer) + }, + Options.ClaimsIssuer); + + if (Options.SaveTokensAsClaims) + { + identity.AddClaim(new Claim("access_token", accessToken.Token, ClaimValueTypes.String, Options.ClaimsIssuer)); + } + + return await CreateTicketAsync(identity, properties, accessToken); } catch (Exception ex) { @@ -118,6 +114,25 @@ public override async Task AuthenticateAsync() return new AuthenticationTicket(properties, Options.AuthenticationScheme); } } + + protected virtual async Task CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, AccessToken token) + { + var notification = new TwitterAuthenticatedContext(Context, token.UserId, token.ScreenName, token.Token, token.TokenSecret) + { + Principal = new ClaimsPrincipal(identity), + Properties = properties + }; + + await Options.Notifications.Authenticated(notification); + + if (notification.Principal?.Identity == null) + { + return null; + } + + return new AuthenticationTicket(notification.Principal, notification.Properties, Options.AuthenticationScheme); + } + protected override async Task HandleUnauthorizedAsync([NotNull] ChallengeContext context) { var properties = new AuthenticationProperties(context.Properties); diff --git a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs index 4768f59b5..73cac0456 100644 --- a/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs +++ b/src/Microsoft.AspNet.Authentication.Twitter/TwitterAuthenticationOptions.cs @@ -3,6 +3,7 @@ using System; using System.Net.Http; +using System.Security.Claims; using Microsoft.AspNet.Http; using Microsoft.AspNet.Authentication.Twitter.Messages; @@ -102,8 +103,16 @@ public string Caption public ISecureDataFormat StateDataFormat { get; set; } /// - /// Gets or sets the used to handle authentication events. + /// Gets or sets the used to handle authentication events. /// public ITwitterAuthenticationNotifications Notifications { get; set; } + + /// + /// Defines whether access tokens should be stored in the + /// after a successful authentication. + /// This property is set to false by default to reduce + /// the size of the final authentication cookie. + /// + public bool SaveTokensAsClaims { get; set; } } } diff --git a/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs index 8e4fe6522..711c601ba 100644 --- a/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/Facebook/FacebookMiddlewareTests.cs @@ -4,6 +4,7 @@ using System; using System.Net; using System.Threading.Tasks; +using Microsoft.AspNet.Authentication.OAuth; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Authentication; @@ -33,7 +34,7 @@ public async Task ChallengeWillTriggerApplyRedirectEvent() { options.AppId = "Test App Id"; options.AppSecret = "Test App Secret"; - options.Notifications = new FacebookAuthenticationNotifications + options.Notifications = new OAuthAuthenticationNotifications { OnApplyRedirect = context => { diff --git a/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs index c48409eed..ee55456dc 100644 --- a/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/Google/GoogleMiddlewareTests.cs @@ -9,6 +9,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Authentication.DataHandler; +using Microsoft.AspNet.Authentication.OAuth; using Microsoft.AspNet.Builder; using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Http; @@ -198,7 +199,7 @@ public async Task ChallengeWillTriggerApplyRedirectEvent() { options.ClientId = "Test Id"; options.ClientSecret = "Test Secret"; - options.Notifications = new GoogleAuthenticationNotifications + options.Notifications = new OAuthAuthenticationNotifications { OnApplyRedirect = context => { @@ -414,14 +415,14 @@ public async Task AuthenticatedEventCanGetRefreshToken() return null; } }; - options.Notifications = new GoogleAuthenticationNotifications() + options.Notifications = new OAuthAuthenticationNotifications { OnAuthenticated = context => - { - var refreshToken = context.RefreshToken; - context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google")); - return Task.FromResult(null); - } + { + var refreshToken = context.RefreshToken; + context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google")); + return Task.FromResult(null); + } }; }); var properties = new AuthenticationProperties(); diff --git a/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs index 253263fb5..42799750c 100644 --- a/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/MicrosoftAccount/MicrosoftAccountMiddlewareTests.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Authentication.DataHandler; using Microsoft.AspNet.Authentication.MicrosoftAccount; +using Microsoft.AspNet.Authentication.OAuth; using Microsoft.AspNet.Builder; using Microsoft.AspNet.DataProtection; using Microsoft.AspNet.Http; @@ -31,7 +32,7 @@ public async Task ChallengeWillTriggerApplyRedirectEvent() { options.ClientId = "Test Client Id"; options.ClientSecret = "Test Client Secret"; - options.Notifications = new MicrosoftAccountAuthenticationNotifications + options.Notifications = new OAuthAuthenticationNotifications { OnApplyRedirect = context => { @@ -143,7 +144,7 @@ public async Task AuthenticatedEventCanGetRefreshToken() return null; } }; - options.Notifications = new MicrosoftAccountAuthenticationNotifications + options.Notifications = new OAuthAuthenticationNotifications { OnAuthenticated = context => {