Revisit OAuthAuthenticationHandler and add a new SaveTokensAsClaims option

This commit is contained in:
Kévin Chalet 2015-05-14 16:03:21 +02:00 committed by Hao Kung
parent 6e67b1c9b1
commit 9bb8b61146
40 changed files with 494 additions and 782 deletions

View File

@ -1,3 +1,4 @@
using System.Linq;
using System.Security.Claims;
using Microsoft.AspNet.Authentication.Cookies;
using Microsoft.AspNet.Builder;
@ -25,10 +26,11 @@ namespace CookieSample
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;

View File

@ -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 @@ namespace CookieSessionSample
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<Claim>(1001);
@ -36,7 +37,10 @@ namespace CookieSessionSample
{
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;

View File

@ -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 @@ namespace OpenIdConnectSample
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 = "/" });

View File

@ -1,3 +1,4 @@
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Security.Claims;
@ -110,6 +111,7 @@ namespace CookieSample
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 @@ namespace CookieSample
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 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 identity = new ClaimsIdentity(
context.Options.AuthenticationScheme,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
var response = await notification.Backchannel.SendAsync(request, notification.HttpContext.RequestAborted);
response.EnsureSuccessStatusCode();
JToken value;
var id = user.TryGetValue("id", out value) ? value.ToString() : null;
if (!string.IsNullOrEmpty(id))
var user = JObject.Parse(await response.Content.ReadAsStringAsync());
var identifier = user.Value<string>("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<string>("login");
if (!string.IsNullOrEmpty(userName))
{
identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, userName, ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var name = user.TryGetValue("name", out value) ? value.ToString() : null;
if (!string.IsNullOrEmpty(name))
{
identity.AddClaim(new Claim("urn:github:name", name, ClaimValueTypes.String, context.Options.ClaimsIssuer));
}
var link = user.TryGetValue("url", out value) ? value.ToString() : null;
if (!string.IsNullOrEmpty(link))
{
identity.AddClaim(new Claim("urn:github:url", link, ClaimValueTypes.String, context.Options.ClaimsIssuer));
notification.Identity.AddClaim(new Claim(
ClaimsIdentity.DefaultNameClaimType, userName,
ClaimValueTypes.String, notification.Options.ClaimsIssuer));
}
context.Principal = new ClaimsPrincipal(identity);
var name = user.Value<string>("name");
if (!string.IsNullOrEmpty(name))
{
notification.Identity.AddClaim(new Claim(
"urn:github:name", name,
ClaimValueTypes.String, notification.Options.ClaimsIssuer));
}
var link = user.Value<string>("url");
if (!string.IsNullOrEmpty(link))
{
notification.Identity.AddClaim(new Claim(
"urn:github:url", link,
ClaimValueTypes.String, notification.Options.ClaimsIssuer));
}
},
};
});
@ -207,8 +214,8 @@ namespace CookieSample
{
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("<html><body>");
await context.Response.WriteAsync("You have been logged out. Goodbye " + context.User.Identity.Name + "<br>");
await context.Response.WriteAsync("<a href=\"/\">Home</a>");
@ -219,7 +226,7 @@ namespace CookieSample
// 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 @@ namespace CookieSample
{
context.Response.ContentType = "text/html";
await context.Response.WriteAsync("<html><body>");
await context.Response.WriteAsync("Hello " + context.User.Identity.Name + "<br>");
await context.Response.WriteAsync("Hello " + (context.User.Identity.Name ?? "anonymous") + "<br>");
foreach (var claim in context.User.Claims)
{
await context.Response.WriteAsync(claim.Type + ": " + claim.Value + "<br>");

View File

@ -17,14 +17,14 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.Facebook
{
internal class FacebookAuthenticationHandler : OAuthAuthenticationHandler<FacebookAuthenticationOptions, IFacebookAuthenticationNotifications>
internal class FacebookAuthenticationHandler : OAuthAuthenticationHandler<FacebookAuthenticationOptions>
{
public FacebookAuthenticationHandler(HttpClient httpClient)
: base(httpClient)
{
}
protected override async Task<TokenResponse> ExchangeCodeAsync(string code, string redirectUri)
protected override async Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
{
var queryBuilder = new QueryBuilder()
{
@ -35,70 +35,78 @@ namespace Microsoft.AspNet.Authentication.Facebook
{ "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<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens)
protected override async Task<AuthenticationTicket> 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 response = await Backchannel.GetAsync(endpoint, Context.RequestAborted);
response.EnsureSuccessStatusCode();
var context = new FacebookAuthenticatedContext(Context, Options, user, tokens);
var identity = new ClaimsIdentity(
Options.ClaimsIssuer,
ClaimsIdentity.DefaultNameClaimType,
ClaimsIdentity.DefaultRoleClaimType);
if (!string.IsNullOrEmpty(context.Id))
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 = FacebookAuthenticationHelper.GetId(payload);
if (!string.IsNullOrEmpty(identifier))
{
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)

View File

@ -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
{
/// <summary>
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/>
/// instance retrieved from Facebook after a successful authentication process.
/// </summary>
public static class FacebookAuthenticationHelper
{
/// <summary>
/// Gets the Facebook user ID.
/// </summary>
public static string GetId([NotNull] JObject user) => user.Value<string>("id");
/// <summary>
/// Gets the user's name.
/// </summary>
public static string GetName([NotNull] JObject user) => user.Value<string>("name");
/// <summary>
/// Gets the user's link.
/// </summary>
public static string GetLink([NotNull] JObject user) => user.Value<string>("link");
/// <summary>
/// Gets the Facebook username.
/// </summary>
public static string GetUserName([NotNull] JObject user) => user.Value<string>("username");
/// <summary>
/// Gets the Facebook email.
/// </summary>
public static string GetEmail([NotNull] JObject user) => user.Value<string>("email");
}
}

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
/// <summary>
/// An ASP.NET middleware for authenticating users using Facebook.
/// </summary>
public class FacebookAuthenticationMiddleware : OAuthAuthenticationMiddleware<FacebookAuthenticationOptions, IFacebookAuthenticationNotifications>
public class FacebookAuthenticationMiddleware : OAuthAuthenticationMiddleware<FacebookAuthenticationOptions>
{
/// <summary>
/// Initializes a new <see cref="FacebookAuthenticationMiddleware"/>.
@ -43,11 +43,6 @@ namespace Microsoft.AspNet.Authentication.Facebook
{
throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Resources.Exception_OptionMustBeProvided, nameof(Options.AppSecret)));
}
if (Options.Notifications == null)
{
Options.Notifications = new FacebookAuthenticationNotifications();
}
}
/// <summary>

View File

@ -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
/// <summary>
/// Configuration options for <see cref="FacebookAuthenticationMiddleware"/>.
/// </summary>
public class FacebookAuthenticationOptions : OAuthAuthenticationOptions<IFacebookAuthenticationNotifications>
public class FacebookAuthenticationOptions : OAuthAuthenticationOptions
{
/// <summary>
/// Initializes a new <see cref="FacebookAuthenticationOptions"/>.
@ -23,6 +24,7 @@ namespace Microsoft.AspNet.Authentication.Facebook
AuthorizationEndpoint = FacebookAuthenticationDefaults.AuthorizationEndpoint;
TokenEndpoint = FacebookAuthenticationDefaults.TokenEndpoint;
UserInformationEndpoint = FacebookAuthenticationDefaults.UserInformationEndpoint;
SaveTokensAsClaims = false;
}
// Facebook uses a non-standard term for this field.

View File

@ -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
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class FacebookAuthenticatedContext : OAuthAuthenticatedContext
{
/// <summary>
/// Initializes a new <see cref="FacebookAuthenticatedContext"/>.
/// </summary>
/// <param name="context">The HTTP environment.</param>
/// <param name="user">The JSON-serialized user.</param>
/// <param name="tokens">The Facebook Access token.</param>
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");
}
/// <summary>
/// Gets the Facebook user ID.
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Gets the user's name.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the user's link.
/// </summary>
public string Link { get; private set; }
/// <summary>
/// Gets the Facebook username.
/// </summary>
public string UserName { get; private set; }
/// <summary>
/// Gets the Facebook email.
/// </summary>
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;
}
}
}

View File

@ -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
{
/// <summary>
/// The default <see cref="IFacebookAuthenticationNotifications"/> implementation.
/// </summary>
public class FacebookAuthenticationNotifications : OAuthAuthenticationNotifications, IFacebookAuthenticationNotifications
{
/// <summary>
/// Initializes a new <see cref="FacebookAuthenticationNotifications"/>.
/// </summary>
public FacebookAuthenticationNotifications()
{
OnAuthenticated = context => Task.FromResult<object>(null);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<FacebookAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Invoked whenever Facebook succesfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(FacebookAuthenticatedContext context)
{
return OnAuthenticated(context);
}
}
}

View File

@ -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
{
/// <summary>
/// Specifies callback methods which the <see cref="FacebookAuthenticationMiddleware"></see> invokes to enable developer control over the authentication process.
/// </summary>
public interface IFacebookAuthenticationNotifications : IOAuthAuthenticationNotifications
{
/// <summary>
/// Invoked when Facebook succesfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task Authenticated(FacebookAuthenticatedContext context);
}
}

View File

@ -14,65 +14,69 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.Google
{
internal class GoogleAuthenticationHandler : OAuthAuthenticationHandler<GoogleAuthenticationOptions, IGoogleAuthenticationNotifications>
internal class GoogleAuthenticationHandler : OAuthAuthenticationHandler<GoogleAuthenticationOptions>
{
public GoogleAuthenticationHandler(HttpClient httpClient)
: base(httpClient)
{
}
protected override async Task<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens)
protected override async Task<AuthenticationTicket> 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);
var response = await Backchannel.SendAsync(request, Context.RequestAborted);
response.EnsureSuccessStatusCode();
if (!string.IsNullOrEmpty(context.Id))
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));
}
if (!string.IsNullOrEmpty(context.GivenName))
{
identity.AddClaim(new Claim(ClaimTypes.GivenName, context.GivenName,
ClaimValueTypes.String, Options.ClaimsIssuer));
}
if (!string.IsNullOrEmpty(context.FamilyName))
{
identity.AddClaim(new Claim(ClaimTypes.Surname, context.FamilyName,
ClaimValueTypes.String, Options.ClaimsIssuer));
}
if (!string.IsNullOrEmpty(context.Name))
{
identity.AddClaim(new Claim(ClaimTypes.Name, context.Name, ClaimValueTypes.String,
Options.ClaimsIssuer));
}
if (!string.IsNullOrEmpty(context.Email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, ClaimValueTypes.String,
Options.ClaimsIssuer));
}
if (!string.IsNullOrEmpty(context.Profile))
{
identity.AddClaim(new Claim("urn:google:profile", context.Profile, ClaimValueTypes.String,
Options.ClaimsIssuer));
}
context.Properties = properties;
context.Principal = new ClaimsPrincipal(identity);
Properties = properties,
Principal = new ClaimsPrincipal(identity)
};
await Options.Notifications.Authenticated(context);
var identifier = GoogleAuthenticationHelper.GetId(payload);
if (!string.IsNullOrEmpty(identifier))
{
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, identifier, ClaimValueTypes.String, Options.ClaimsIssuer));
}
return new AuthenticationTicket(context.Principal, context.Properties, context.Options.AuthenticationScheme);
var givenName = GoogleAuthenticationHelper.GetGivenName(payload);
if (!string.IsNullOrEmpty(givenName))
{
identity.AddClaim(new Claim(ClaimTypes.GivenName, givenName, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var familyName = GoogleAuthenticationHelper.GetFamilyName(payload);
if (!string.IsNullOrEmpty(familyName))
{
identity.AddClaim(new Claim(ClaimTypes.Surname, familyName, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var name = GoogleAuthenticationHelper.GetName(payload);
if (!string.IsNullOrEmpty(name))
{
identity.AddClaim(new Claim(ClaimTypes.Name, name, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var email = GoogleAuthenticationHelper.GetEmail(payload);
if (!string.IsNullOrEmpty(email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer));
}
var profile = GoogleAuthenticationHelper.GetProfile(payload);
if (!string.IsNullOrEmpty(profile))
{
identity.AddClaim(new Claim("urn:google:profile", profile, ClaimValueTypes.String, Options.ClaimsIssuer));
}
await Options.Notifications.Authenticated(notification);
return new AuthenticationTicket(notification.Principal, notification.Properties, notification.Options.AuthenticationScheme);
}
// TODO: Abstract this properties override pattern into the base class?

View File

@ -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
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/>
/// instance retrieved from Google after a successful authentication process.
/// </summary>
public class GoogleAuthenticatedContext : OAuthAuthenticatedContext
public static class GoogleAuthenticationHelper
{
/// <summary>
/// Initializes a new <see cref="GoogleAuthenticatedContext"/>.
/// </summary>
/// <param name="context">The HTTP environment.</param>
/// <param name="user">The JSON-serialized Google user info.</param>
/// <param name="tokens">Google OAuth 2.0 access token, refresh token, etc.</param>
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");
}
/// <summary>
/// Gets the Google user ID.
/// </summary>
public string Id { get; private set; }
public static string GetId([NotNull] JObject user) => user.Value<string>("id");
/// <summary>
/// Gets the user's name.
/// </summary>
public string Name { get; private set; }
public static string GetName([NotNull] JObject user) => user.Value<string>("displayName");
/// <summary>
/// Gets the user's given name.
/// </summary>
public string GivenName { get; set; }
public static string GetGivenName([NotNull] JObject user) => TryGetValue(user, "name", "givenName");
/// <summary>
/// Gets the user's family name.
/// </summary>
public string FamilyName { get; set; }
public static string GetFamilyName([NotNull] JObject user) => TryGetValue(user, "name", "familyName");
/// <summary>
/// Gets the user's profile link.
/// </summary>
public string Profile { get; private set; }
public static string GetProfile([NotNull] JObject user) => user.Value<string>("url");
/// <summary>
/// Gets the user's email.
/// </summary>
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)

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Authentication.Google
/// An ASP.NET middleware for authenticating users using Google OAuth 2.0.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Middleware are not disposable.")]
public class GoogleAuthenticationMiddleware : OAuthAuthenticationMiddleware<GoogleAuthenticationOptions, IGoogleAuthenticationNotifications>
public class GoogleAuthenticationMiddleware : OAuthAuthenticationMiddleware<GoogleAuthenticationOptions>
{
/// <summary>
/// Initializes a new <see cref="GoogleAuthenticationMiddleware"/>.
@ -35,11 +35,6 @@ namespace Microsoft.AspNet.Authentication.Google
ConfigureOptions<GoogleAuthenticationOptions> 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

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.Google
/// <summary>
/// Configuration options for <see cref="GoogleAuthenticationMiddleware"/>.
/// </summary>
public class GoogleAuthenticationOptions : OAuthAuthenticationOptions<IGoogleAuthenticationNotifications>
public class GoogleAuthenticationOptions : OAuthAuthenticationOptions
{
/// <summary>
/// Initializes a new <see cref="GoogleAuthenticationOptions"/>.
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Authentication.Google
AuthorizationEndpoint = GoogleAuthenticationDefaults.AuthorizationEndpoint;
TokenEndpoint = GoogleAuthenticationDefaults.TokenEndpoint;
UserInformationEndpoint = GoogleAuthenticationDefaults.UserInformationEndpoint;
SaveTokensAsClaims = false;
}
/// <summary>

View File

@ -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
{
/// <summary>
/// The default <see cref="IGoogleAuthenticationNotifications"/> implementation.
/// </summary>
public class GoogleAuthenticationNotifications : OAuthAuthenticationNotifications, IGoogleAuthenticationNotifications
{
/// <summary>
/// Initializes a new <see cref="GoogleAuthenticationNotifications"/>.
/// </summary>
public GoogleAuthenticationNotifications()
{
OnAuthenticated = context => Task.FromResult<object>(null);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<GoogleAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Invoked whenever Google succesfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(GoogleAuthenticatedContext context)
{
return OnAuthenticated(context);
}
}
}

View File

@ -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
{
/// <summary>
/// Specifies callback methods which the <see cref="GoogleAuthenticationMiddleware" /> invokes to enable developer control over the authentication process.
/// </summary>
public interface IGoogleAuthenticationNotifications : IOAuthAuthenticationNotifications
{
/// <summary>
/// Invoked whenever Google succesfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task Authenticated(GoogleAuthenticatedContext context);
}
}

View File

@ -11,45 +11,52 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.MicrosoftAccount
{
internal class MicrosoftAccountAuthenticationHandler : OAuthAuthenticationHandler<MicrosoftAccountAuthenticationOptions, IMicrosoftAccountAuthenticationNotifications>
internal class MicrosoftAccountAuthenticationHandler : OAuthAuthenticationHandler<MicrosoftAccountAuthenticationOptions>
{
public MicrosoftAccountAuthenticationHandler(HttpClient httpClient)
: base(httpClient)
{
}
protected override async Task<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens)
protected override async Task<AuthenticationTicket> 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);
var response = await Backchannel.SendAsync(request, Context.RequestAborted);
response.EnsureSuccessStatusCode();
if (!string.IsNullOrWhiteSpace(context.Email))
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var notification = new OAuthAuthenticatedContext(Context, Options, Backchannel, tokens, payload)
{
identity.AddClaim(new Claim(ClaimTypes.Email, context.Email, ClaimValueTypes.String, Options.ClaimsIssuer));
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));
}
context.Principal = new ClaimsPrincipal(identity);
await Options.Notifications.Authenticated(context);
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));
}
return new AuthenticationTicket(context.Principal, context.Properties, context.Options.AuthenticationScheme);
var email = MicrosoftAccountAuthenticationHelper.GetEmail(payload);
if (!string.IsNullOrWhiteSpace(email))
{
identity.AddClaim(new Claim(ClaimTypes.Email, email, ClaimValueTypes.String, Options.ClaimsIssuer));
}
await Options.Notifications.Authenticated(notification);
return new AuthenticationTicket(notification.Principal, notification.Properties, notification.Options.AuthenticationScheme);
}
}
}

View File

@ -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
{
/// <summary>
/// Contains static methods that allow to extract user's information from a <see cref="JObject"/>
/// instance retrieved from Google after a successful authentication process.
/// </summary>
public static class MicrosoftAccountAuthenticationHelper
{
/// <summary>
/// Gets the Microsoft Account user ID.
/// </summary>
public static string GetId([NotNull] JObject user) => user.Value<string>("id");
/// <summary>
/// Gets the user's name.
/// </summary>
public static string GetName([NotNull] JObject user) => user.Value<string>("name");
/// <summary>
/// Gets the user's first name.
/// </summary>
public static string GetFirstName([NotNull] JObject user) => user.Value<string>("first_name");
/// <summary>
/// Gets the user's last name.
/// </summary>
public static string GetLastName([NotNull] JObject user) => user.Value<string>("last_name");
/// <summary>
/// Gets the user's email address.
/// </summary>
public static string GetEmail([NotNull] JObject user) => user.Value<JObject>("emails")
?.Value<string>("preferred");
}
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
/// <summary>
/// An ASP.NET middleware for authenticating users using the Microsoft Account service.
/// </summary>
public class MicrosoftAccountAuthenticationMiddleware : OAuthAuthenticationMiddleware<MicrosoftAccountAuthenticationOptions, IMicrosoftAccountAuthenticationNotifications>
public class MicrosoftAccountAuthenticationMiddleware : OAuthAuthenticationMiddleware<MicrosoftAccountAuthenticationOptions>
{
/// <summary>
/// Initializes a new <see cref="MicrosoftAccountAuthenticationMiddleware"/>.
@ -33,10 +33,6 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
ConfigureOptions<MicrosoftAccountAuthenticationOptions> 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.

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
/// <summary>
/// Configuration options for <see cref="MicrosoftAccountAuthenticationMiddleware"/>.
/// </summary>
public class MicrosoftAccountAuthenticationOptions : OAuthAuthenticationOptions<IMicrosoftAccountAuthenticationNotifications>
public class MicrosoftAccountAuthenticationOptions : OAuthAuthenticationOptions
{
/// <summary>
/// Initializes a new <see cref="MicrosoftAccountAuthenticationOptions"/>.
@ -22,6 +22,7 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
AuthorizationEndpoint = MicrosoftAccountAuthenticationDefaults.AuthorizationEndpoint;
TokenEndpoint = MicrosoftAccountAuthenticationDefaults.TokenEndpoint;
UserInformationEndpoint = MicrosoftAccountAuthenticationDefaults.UserInformationEndpoint;
SaveTokensAsClaims = false;
}
}
}

View File

@ -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
{
/// <summary>
/// Specifies callback methods which the <see cref="MicrosoftAccountAuthenticationMiddleware"/> invokes to enable developer control over the authentication process.
/// </summary>
public interface IMicrosoftAccountAuthenticationNotifications : IOAuthAuthenticationNotifications
{
/// <summary>
/// Invoked whenever Microsoft succesfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task Authenticated(MicrosoftAccountAuthenticatedContext context);
}
}

View File

@ -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
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class MicrosoftAccountAuthenticatedContext : OAuthAuthenticatedContext
{
/// <summary>
/// Initializes a new <see cref="MicrosoftAccountAuthenticatedContext"/>.
/// </summary>
/// <param name="context">The HTTP environment.</param>
/// <param name="user">The JSON-serialized user.</param>
/// <param name="tokens">The access token provided by the Microsoft authentication service.</param>
public MicrosoftAccountAuthenticatedContext(HttpContext context, OAuthAuthenticationOptions options, [NotNull] JObject user, TokenResponse tokens)
: base(context, options, user, tokens)
{
IDictionary<string, JToken> 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<JProperty>().Where(childAsProperty => childAsProperty.Name == "preferred"))
{
Email = childAsProperty.Value.ToString();
}
}
}
/// <summary>
/// Gets the Microsoft Account user ID.
/// </summary>
public string Id { get; private set; }
/// <summary>
/// Gets the user's name.
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the user's first name.
/// </summary>
public string FirstName { get; private set; }
/// <summary>
/// Gets the user's last name.
/// </summary>
public string LastName { get; private set; }
/// <summary>
/// Gets the user's email address.
/// </summary>
public string Email { get; private set; }
private static string PropertyValueIfExists(string property, IDictionary<string, JToken> dictionary)
{
return dictionary.ContainsKey(property) ? dictionary[property].ToString() : null;
}
}
}

View File

@ -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
{
/// <summary>
/// Default <see cref="IMicrosoftAccountAuthenticationNotifications"/> implementation.
/// </summary>
public class MicrosoftAccountAuthenticationNotifications : OAuthAuthenticationNotifications, IMicrosoftAccountAuthenticationNotifications
{
/// <summary>
/// Initializes a new <see cref="MicrosoftAccountAuthenticationNotifications"/>
/// </summary>
public MicrosoftAccountAuthenticationNotifications()
{
OnAuthenticated = context => Task.FromResult(0);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<MicrosoftAccountAuthenticatedContext, Task> OnAuthenticated { get; set; }
/// <summary>
/// Invoked whenever Microsoft succesfully authenticates a user
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task Authenticated(MicrosoftAccountAuthenticatedContext context)
{
return OnAuthenticated(context);
}
}
}

View File

@ -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 @@ namespace Microsoft.AspNet.Authentication.OAuth
{
/// <summary>
/// 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.
/// </summary>
/// <param name="context">Contains information about the login session.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task GetUserInformationAsync(OAuthGetUserInformationContext context);
Task Authenticated(OAuthAuthenticatedContext context);
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// Invoked prior to the <see cref="ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context"></param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
Task ReturnEndpoint(OAuthReturnEndpointContext context);
/// <summary>
/// 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.
/// </summary>
/// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge.</param>
void ApplyRedirect(OAuthApplyRedirectContext context);

View File

@ -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 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// Initializes a new <see cref="OAuthAuthenticatedContext"/>.
/// </summary>
/// <param name="context">The HTTP environment.</param>
/// <param name="user">The JSON-serialized user.</param>
/// <param name="options">The options used by the authentication middleware.</param>
/// <param name="backchannel">The HTTP client used by the authentication middleware</param>
/// <param name="tokens">The tokens returned from the token endpoint.</param>
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);
}
}
/// <summary>
/// Gets the JSON-serialized user.
/// Initializes a new <see cref="OAuthAuthenticatedContext"/>.
/// </summary>
public JObject User { get; protected set; }
/// <param name="context">The HTTP environment.</param>
/// <param name="options">The options used by the authentication middleware.</param>
/// <param name="backchannel">The HTTP client used by the authentication middleware</param>
/// <param name="tokens">The tokens returned from the token endpoint.</param>
/// <param name="user">The JSON-serialized user.</param>
public OAuthAuthenticatedContext(
[NotNull] HttpContext context,
[NotNull] OAuthAuthenticationOptions options,
[NotNull] HttpClient backchannel,
[NotNull] OAuthTokenResponse tokens,
[NotNull] JObject user)
: base(context, options)
{
TokenResponse = tokens;
Backchannel = backchannel;
}
/// <summary>
/// Gets the JSON-serialized user or an empty
/// <see cref="JObject"/> if it is not available.
/// </summary>
public JObject User { get; }
/// <summary>
/// Gets the token response returned by the authentication service.
/// </summary>
public OAuthTokenResponse TokenResponse { get; }
/// <summary>
/// Gets the access token provided by the authentication service.
/// </summary>
public string AccessToken { get; protected set; }
public string AccessToken => TokenResponse.AccessToken;
/// <summary>
/// Gets the access token type provided by the authentication service.
/// </summary>
public string TokenType { get; protected set; }
public string TokenType => TokenResponse.TokenType;
/// <summary>
/// Gets the refresh token provided by the authentication service.
/// </summary>
public string RefreshToken { get; protected set; }
public string RefreshToken => TokenResponse.RefreshToken;
/// <summary>
/// Gets the access token expiration time.
/// </summary>
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;
}
}
/// <summary>
/// Gets the backchannel used to communicate with the provider.
/// </summary>
public HttpClient Backchannel { get; }
/// <summary>
/// Gets the <see cref="ClaimsIdentity"/> representing the user.
/// Gets the <see cref="ClaimsPrincipal"/> representing the user.
/// </summary>
public ClaimsPrincipal Principal { get; set; }
/// <summary>
/// Gets the main identity exposed by <see cref="Principal"/>.
/// This property returns <c>null</c> when <see cref="Principal"/> is <c>null</c>.
/// </summary>
public ClaimsIdentity Identity => Principal?.Identity as ClaimsIdentity;
/// <summary>
/// Gets or sets a property bag for common authentication properties.
/// </summary>

View File

@ -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
/// </summary>
public class OAuthAuthenticationNotifications : IOAuthAuthenticationNotifications
{
/// <summary>
/// Initializes a new <see cref="OAuthAuthenticationNotifications"/>
/// </summary>
public OAuthAuthenticationNotifications()
{
OnGetUserInformationAsync = OAuthAuthenticationDefaults.DefaultOnGetUserInformationAsync;
OnReturnEndpoint = context => Task.FromResult(0);
OnApplyRedirect = context => context.Response.Redirect(context.RedirectUri);
}
/// <summary>
/// Gets or sets the function that is invoked when the Authenticated method is invoked.
/// </summary>
public Func<OAuthGetUserInformationContext, Task> OnGetUserInformationAsync { get; set; }
public Func<OAuthAuthenticatedContext, Task> OnAuthenticated { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Gets or sets the function that is invoked when the ReturnEndpoint method is invoked.
/// </summary>
public Func<OAuthReturnEndpointContext, Task> OnReturnEndpoint { get; set; }
public Func<OAuthReturnEndpointContext, Task> OnReturnEndpoint { get; set; } = context => Task.FromResult(0);
/// <summary>
/// Gets or sets the delegate that is invoked when the ApplyRedirect method is invoked.
/// </summary>
public Action<OAuthApplyRedirectContext> OnApplyRedirect { get; set; }
public Action<OAuthApplyRedirectContext> OnApplyRedirect { get; set; } = context => context.Response.Redirect(context.RedirectUri);
/// <summary>
/// Invoked after the provider successfully authenticates a user.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.</param>
/// <param name="context">Contains information about the login session as well as the user <see cref="ClaimsIdentity"/>.</param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task GetUserInformationAsync(OAuthGetUserInformationContext context)
{
return OnGetUserInformationAsync(context);
}
public virtual Task Authenticated(OAuthAuthenticatedContext context) => OnAuthenticated(context);
/// <summary>
/// Invoked prior to the <see cref="System.Security.Claims.ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// Invoked prior to the <see cref="ClaimsIdentity"/> being saved in a local cookie and the browser being redirected to the originally requested URL.
/// </summary>
/// <param name="context">Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/></param>
/// <param name="context">Contains information about the login session as well as the user <see cref="ClaimsIdentity"/></param>
/// <returns>A <see cref="Task"/> representing the completed operation.</returns>
public virtual Task ReturnEndpoint(OAuthReturnEndpointContext context)
{
return OnReturnEndpoint(context);
}
public virtual Task ReturnEndpoint(OAuthReturnEndpointContext context) => OnReturnEndpoint(context);
/// <summary>
/// Called when a Challenge causes a redirect to authorize endpoint in the OAuth middleware.
/// </summary>
/// <param name="context">Contains redirect URI and <see cref="AuthenticationProperties"/> of the challenge.</param>
public virtual void ApplyRedirect(OAuthApplyRedirectContext context)
{
OnApplyRedirect(context);
}
public virtual void ApplyRedirect(OAuthApplyRedirectContext context) => OnApplyRedirect(context);
}
}

View File

@ -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
{
/// <summary>
/// Contains information about the login session as well as the user <see cref="System.Security.Claims.ClaimsIdentity"/>.
/// </summary>
public class OAuthGetUserInformationContext : BaseContext<OAuthAuthenticationOptions>
{
/// <summary>
/// Initializes a new <see cref="OAuthGetUserInformationContext"/>.
/// </summary>
/// <param name="context">The HTTP environment.</param>
/// <param name="user">The JSON-serialized user.</param>
/// <param name="tokens">The tokens returned from the token endpoint.</param>
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);
}
}
/// <summary>
/// Gets the access token provided by the authentication service.
/// </summary>
public string AccessToken { get; protected set; }
/// <summary>
/// Gets the access token type provided by the authentication service.
/// </summary>
public string TokenType { get; protected set; }
/// <summary>
/// Gets the refresh token provided by the authentication service.
/// </summary>
public string RefreshToken { get; protected set; }
/// <summary>
/// Gets the access token expiration time.
/// </summary>
public TimeSpan? ExpiresIn { get; protected set; }
/// <summary>
/// Gets the backchannel used to communicate with the provider.
/// </summary>
public HttpClient Backchannel { get; protected set; }
/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> representing the user.
/// </summary>
public ClaimsPrincipal Principal { get; set; }
/// <summary>
/// Gets or sets a property bag for common authentication properties.
/// </summary>
public AuthenticationProperties Properties { get; set; }
}
}

View File

@ -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<OAuthGetUserInformationContext, Task> 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);
};
}
}

View File

@ -19,10 +19,10 @@ namespace Microsoft.AspNet.Builder
/// <param name="app">The <see cref="IApplicationBuilder"/> passed to the configure method.</param>
/// <param name="options">The middleware configuration options.</param>
/// <returns>The updated <see cref="IApplicationBuilder"/>.</returns>
public static IApplicationBuilder UseOAuthAuthentication([NotNull] this IApplicationBuilder app, [NotNull] string authenticationScheme, Action<OAuthAuthenticationOptions<IOAuthAuthenticationNotifications>> configureOptions = null)
public static IApplicationBuilder UseOAuthAuthentication([NotNull] this IApplicationBuilder app, [NotNull] string authenticationScheme, Action<OAuthAuthenticationOptions> configureOptions = null)
{
return app.UseMiddleware<OAuthAuthenticationMiddleware<OAuthAuthenticationOptions<IOAuthAuthenticationNotifications>, IOAuthAuthenticationNotifications>>(
new ConfigureOptions<OAuthAuthenticationOptions<IOAuthAuthenticationNotifications>>(options =>
return app.UseMiddleware<OAuthAuthenticationMiddleware<OAuthAuthenticationOptions>>(
new ConfigureOptions<OAuthAuthenticationOptions>(options =>
{
options.AuthenticationScheme = authenticationScheme;
options.Caption = authenticationScheme;
@ -30,10 +30,6 @@ namespace Microsoft.AspNet.Builder
{
configureOptions(options);
}
if (options.Notifications == null)
{
options.Notifications = new OAuthAuthenticationNotifications();
}
})
{
Name = authenticationScheme,

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
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 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.OAuth
{
public class OAuthAuthenticationHandler<TOptions, TNotifications> : AuthenticationHandler<TOptions>
where TOptions : OAuthAuthenticationOptions<TNotifications>
where TNotifications : IOAuthAuthenticationNotifications
public class OAuthAuthenticationHandler<TOptions> : AuthenticationHandler<TOptions> where TOptions : OAuthAuthenticationOptions
{
private static readonly RandomNumberGenerator CryptoRandom = RandomNumberGenerator.Create();
@ -124,7 +123,33 @@ namespace Microsoft.AspNet.Authentication.OAuth
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 @@ namespace Microsoft.AspNet.Authentication.OAuth
}
}
protected virtual async Task<TokenResponse> ExchangeCodeAsync(string code, string redirectUri)
protected virtual async Task<OAuthTokenResponse> ExchangeCodeAsync(string code, string redirectUri)
{
var tokenRequestParameters = new Dictionary<string, string>()
{
@ -151,20 +176,27 @@ namespace Microsoft.AspNet.Authentication.OAuth
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<AuthenticationTicket> GetUserInformationAsync(AuthenticationProperties properties, TokenResponse tokens)
protected virtual async Task<AuthenticationTicket> 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<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)

View File

@ -19,9 +19,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// An ASP.NET middleware for authenticating users using OAuth services.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Middleware are not disposable.")]
public class OAuthAuthenticationMiddleware<TOptions, TNotifications> : AuthenticationMiddleware<TOptions>
where TOptions : OAuthAuthenticationOptions<TNotifications>, new()
where TNotifications : IOAuthAuthenticationNotifications
public class OAuthAuthenticationMiddleware<TOptions> : AuthenticationMiddleware<TOptions> where TOptions : OAuthAuthenticationOptions, new()
{
/// <summary>
/// Initializes a new <see cref="OAuthAuthenticationMiddleware"/>.
@ -58,12 +56,12 @@ namespace Microsoft.AspNet.Authentication.OAuth
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 @@ namespace Microsoft.AspNet.Authentication.OAuth
{
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 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <returns>An <see cref="AuthenticationHandler"/> configured with the <see cref="OAuthAuthenticationOptions"/> supplied to the constructor.</returns>
protected override AuthenticationHandler<TOptions> CreateHandler()
{
return new OAuthAuthenticationHandler<TOptions, TNotifications>(Backchannel);
return new OAuthAuthenticationHandler<TOptions>(Backchannel);
}
[SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Managed by caller")]
private static HttpMessageHandler ResolveHttpMessageHandler(OAuthAuthenticationOptions<TNotifications> options)
private static HttpMessageHandler ResolveHttpMessageHandler(OAuthAuthenticationOptions options)
{
HttpMessageHandler handler = options.BackchannelHttpHandler ??
#if DNX451

View File

@ -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
/// </summary>
public class OAuthAuthenticationOptions : AuthenticationOptions
{
/// <summary>
/// Initializes a new <see cref="OAuthAuthenticationOptions"/>.
/// </summary>
public OAuthAuthenticationOptions()
{
Scope = new List<string>();
BackchannelTimeout = TimeSpan.FromSeconds(60);
}
/// <summary>
/// Gets or sets the provider-assigned client id.
/// </summary>
@ -46,7 +39,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary>
/// 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.
/// </summary>
public string UserInformationEndpoint { get; set; }
@ -62,6 +55,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// validating the subject name and if the signing chain is a trusted party.</remarks>
public ICertificateValidator BackchannelCertificateValidator { get; set; }
#endif
/// <summary>
/// Get or sets the text that the user can display on a sign in user interface.
/// </summary>
@ -77,7 +71,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <value>
/// The back channel timeout.
/// </value>
public TimeSpan BackchannelTimeout { get; set; }
public TimeSpan BackchannelTimeout { get; set; } = TimeSpan.FromSeconds(60);
/// <summary>
/// The HttpMessageHandler used to communicate with the auth provider.
@ -86,10 +80,15 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// </summary>
public HttpMessageHandler BackchannelHttpHandler { get; set; }
/// <summary>
/// Gets or sets the <see cref="IOAuthAuthenticationNotifications"/> used to handle authentication events.
/// </summary>
public IOAuthAuthenticationNotifications Notifications { get; [param: NotNull] set; } = new OAuthAuthenticationNotifications();
/// <summary>
/// A list of permissions to request.
/// </summary>
public IList<string> Scope { get; private set; }
public IList<string> Scope { get; } = new List<string>();
/// <summary>
/// The request path within the application's base path where the user-agent will be returned.
@ -109,5 +108,13 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// Gets or sets the type used to secure data handled by the middleware.
/// </summary>
public ISecureDataFormat<AuthenticationProperties> StateDataFormat { get; set; }
/// <summary>
/// Defines whether access and refresh tokens should be stored in the
/// <see cref="ClaimsPrincipal"/> after a successful authentication.
/// You can set this property to <c>false</c> to reduce the size of the final
/// authentication cookie. Note that social providers set this property to <c>false</c> by default.
/// </summary>
public bool SaveTokensAsClaims { get; set; } = true;
}
}

View File

@ -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
{
/// <summary>
/// Configuration options for <see cref="OAuthAuthenticationMiddleware"/>.
/// </summary>
public class OAuthAuthenticationOptions<TNotifications> : OAuthAuthenticationOptions where TNotifications : IOAuthAuthenticationNotifications
{
/// <summary>
/// Gets or sets the <see cref="IOAuthAuthenticationNotifications"/> used to handle authentication events.
/// </summary>
public TNotifications Notifications { get; set; }
}
}

View File

@ -5,9 +5,9 @@ using Newtonsoft.Json.Linq;
namespace Microsoft.AspNet.Authentication.OAuth
{
public class TokenResponse
public class OAuthTokenResponse
{
public TokenResponse(JObject response)
public OAuthTokenResponse(JObject response)
{
Response = response;
AccessToken = response.Value<string>("access_token");

View File

@ -82,24 +82,6 @@ namespace Microsoft.AspNet.Authentication.Twitter
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 @@ namespace Microsoft.AspNet.Authentication.Twitter
Response.Cookies.Delete(StateCookie, cookieOptions);
await Options.Notifications.Authenticated(context);
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);
return new AuthenticationTicket(context.Principal, context.Properties, Options.AuthenticationScheme);
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 @@ namespace Microsoft.AspNet.Authentication.Twitter
return new AuthenticationTicket(properties, Options.AuthenticationScheme);
}
}
protected virtual async Task<AuthenticationTicket> 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<bool> HandleUnauthorizedAsync([NotNull] ChallengeContext context)
{
var properties = new AuthenticationProperties(context.Properties);

View File

@ -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 @@ namespace Microsoft.AspNet.Authentication.Twitter
public ISecureDataFormat<RequestToken> StateDataFormat { get; set; }
/// <summary>
/// Gets or sets the <see cref="ITwitterAuthenticationProvider"/> used to handle authentication events.
/// Gets or sets the <see cref="ITwitterAuthenticationNotifications"/> used to handle authentication events.
/// </summary>
public ITwitterAuthenticationNotifications Notifications { get; set; }
/// <summary>
/// Defines whether access tokens should be stored in the
/// <see cref="ClaimsPrincipal"/> after a successful authentication.
/// This property is set to <c>false</c> by default to reduce
/// the size of the final authentication cookie.
/// </summary>
public bool SaveTokensAsClaims { get; set; }
}
}

View File

@ -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 @@ namespace Microsoft.AspNet.Authentication.Facebook
{
options.AppId = "Test App Id";
options.AppSecret = "Test App Secret";
options.Notifications = new FacebookAuthenticationNotifications
options.Notifications = new OAuthAuthenticationNotifications
{
OnApplyRedirect = context =>
{

View File

@ -9,6 +9,7 @@ using System.Security.Claims;
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 @@ namespace Microsoft.AspNet.Authentication.Google
{
options.ClientId = "Test Id";
options.ClientSecret = "Test Secret";
options.Notifications = new GoogleAuthenticationNotifications
options.Notifications = new OAuthAuthenticationNotifications
{
OnApplyRedirect = context =>
{
@ -414,14 +415,14 @@ namespace Microsoft.AspNet.Authentication.Google
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<object>(null);
}
{
var refreshToken = context.RefreshToken;
context.Principal.AddIdentity(new ClaimsIdentity(new Claim[] { new Claim("RefreshToken", refreshToken, ClaimValueTypes.String, "Google") }, "Google"));
return Task.FromResult<object>(null);
}
};
});
var properties = new AuthenticationProperties();

View File

@ -8,6 +8,7 @@ using System.Text;
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 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
{
options.ClientId = "Test Client Id";
options.ClientSecret = "Test Client Secret";
options.Notifications = new MicrosoftAccountAuthenticationNotifications
options.Notifications = new OAuthAuthenticationNotifications
{
OnApplyRedirect = context =>
{
@ -143,7 +144,7 @@ namespace Microsoft.AspNet.Authentication.Tests.MicrosoftAccount
return null;
}
};
options.Notifications = new MicrosoftAccountAuthenticationNotifications
options.Notifications = new OAuthAuthenticationNotifications
{
OnAuthenticated = context =>
{