Expose AuthenticationProperties in events

This commit is contained in:
Hao Kung 2016-01-05 14:32:31 -08:00
parent f195ed3bab
commit 3257a82367
19 changed files with 138 additions and 67 deletions

View File

@ -186,7 +186,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
cookieValue,
cookieOptions);
await ApplyHeaders(shouldRedirectToReturnUrl: false);
await ApplyHeaders(shouldRedirectToReturnUrl: false, properties: ticket.Properties);
}
}
@ -261,7 +261,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
// Only redirect on the login path
var shouldRedirect = Options.LoginPath.HasValue && OriginalPath == Options.LoginPath;
await ApplyHeaders(shouldRedirect);
await ApplyHeaders(shouldRedirect, signedInContext.Properties);
}
protected override async Task HandleSignOutAsync(SignOutContext signOutContext)
@ -277,6 +277,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
var context = new CookieSigningOutContext(
Context,
Options,
new AuthenticationProperties(signOutContext.Properties),
cookieOptions);
await Options.Events.SigningOut(context);
@ -288,10 +289,10 @@ namespace Microsoft.AspNet.Authentication.Cookies
// Only redirect on the logout path
var shouldRedirect = Options.LogoutPath.HasValue && OriginalPath == Options.LogoutPath;
await ApplyHeaders(shouldRedirect);
await ApplyHeaders(shouldRedirect, context.Properties);
}
private async Task ApplyHeaders(bool shouldRedirectToReturnUrl)
private async Task ApplyHeaders(bool shouldRedirectToReturnUrl, AuthenticationProperties properties)
{
Response.Headers[HeaderNames.CacheControl] = HeaderValueNoCache;
Response.Headers[HeaderNames.Pragma] = HeaderValueNoCache;
@ -303,7 +304,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
if (!StringValues.IsNullOrEmpty(redirectUri)
&& IsHostRelative(redirectUri))
{
var redirectContext = new CookieRedirectContext(Context, Options, redirectUri);
var redirectContext = new CookieRedirectContext(Context, Options, redirectUri, properties);
await Options.Events.RedirectToReturnUrl(redirectContext);
}
}
@ -325,13 +326,14 @@ namespace Microsoft.AspNet.Authentication.Cookies
protected override async Task<bool> HandleForbiddenAsync(ChallengeContext context)
{
var returnUrl = new AuthenticationProperties(context.Properties).RedirectUri;
var properties = new AuthenticationProperties(context.Properties);
var returnUrl = properties.RedirectUri;
if (string.IsNullOrEmpty(returnUrl))
{
returnUrl = OriginalPathBase + Request.Path + Request.QueryString;
}
var accessDeniedUri = Options.AccessDeniedPath + QueryString.Create(Options.ReturnUrlParameter, returnUrl);
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(accessDeniedUri));
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(accessDeniedUri), properties);
await Options.Events.RedirectToAccessDenied(redirectContext);
return true;
}
@ -343,14 +345,15 @@ namespace Microsoft.AspNet.Authentication.Cookies
throw new ArgumentNullException(nameof(context));
}
var redirectUri = new AuthenticationProperties(context.Properties).RedirectUri;
var properties = new AuthenticationProperties(context.Properties);
var redirectUri = properties.RedirectUri;
if (string.IsNullOrEmpty(redirectUri))
{
redirectUri = OriginalPathBase + Request.Path + Request.QueryString;
}
var loginUri = Options.LoginPath + QueryString.Create(Options.ReturnUrlParameter, redirectUri);
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(loginUri));
var redirectContext = new CookieRedirectContext(Context, Options, BuildRedirectUri(loginUri), properties);
await Options.Events.RedirectToLogin(redirectContext);
return true;

View File

@ -3,6 +3,7 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
namespace Microsoft.AspNet.Authentication.Cookies
{
@ -18,10 +19,11 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <param name="options">The cookie middleware options</param>
/// <param name="redirectUri">The initial redirect URI</param>
[SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "2#", Justification = "Represents header value")]
public CookieRedirectContext(HttpContext context, CookieAuthenticationOptions options, string redirectUri)
public CookieRedirectContext(HttpContext context, CookieAuthenticationOptions options, string redirectUri, AuthenticationProperties properties)
: base(context, options)
{
RedirectUri = redirectUri;
Properties = properties;
}
/// <summary>
@ -29,5 +31,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Represents header value")]
public string RedirectUri { get; set; }
public AuthenticationProperties Properties { get; }
}
}

View File

@ -36,16 +36,16 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary>
/// The name of the AuthenticationScheme creating a cookie
/// </summary>
public string AuthenticationScheme { get; private set; }
public string AuthenticationScheme { get; }
/// <summary>
/// Contains the claims that were converted into the outgoing cookie.
/// </summary>
public ClaimsPrincipal Principal { get; private set; }
public ClaimsPrincipal Principal { get; }
/// <summary>
/// Contains the extra data that was contained in the outgoing cookie.
/// </summary>
public AuthenticationProperties Properties { get; private set; }
public AuthenticationProperties Properties { get; }
}
}

View File

@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <summary>
/// The name of the AuthenticationScheme creating a cookie
/// </summary>
public string AuthenticationScheme { get; private set; }
public string AuthenticationScheme { get; }
/// <summary>
/// Contains the claims about to be converted into the outgoing cookie.

View File

@ -2,6 +2,7 @@
// 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.Http.Authentication;
namespace Microsoft.AspNet.Authentication.Cookies
{
@ -16,20 +17,23 @@ namespace Microsoft.AspNet.Authentication.Cookies
/// <param name="context"></param>
/// <param name="options"></param>
/// <param name="cookieOptions"></param>
public CookieSigningOutContext(HttpContext context, CookieAuthenticationOptions options, CookieOptions cookieOptions)
public CookieSigningOutContext(
HttpContext context,
CookieAuthenticationOptions options,
AuthenticationProperties properties,
CookieOptions cookieOptions)
: base(context, options)
{
CookieOptions = cookieOptions;
Properties = properties;
}
/// <summary>
/// The options for creating the outgoing cookie.
/// May be replace or altered during the SigningOut call.
/// </summary>
public CookieOptions CookieOptions
{
get;
set;
}
public CookieOptions CookieOptions { get; set; }
public AuthenticationProperties Properties { get; set; }
}
}

View File

@ -37,12 +37,8 @@ namespace Microsoft.AspNet.Authentication.Facebook
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(Context, Options, Backchannel, tokens, payload)
{
Properties = properties,
Principal = new ClaimsPrincipal(identity)
};
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Options, Backchannel, tokens, payload);
var identifier = FacebookHelper.GetId(payload);
if (!string.IsNullOrEmpty(identifier))

View File

@ -31,12 +31,8 @@ namespace Microsoft.AspNet.Authentication.Google
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(Context, Options, Backchannel, tokens, payload)
{
Properties = properties,
Principal = new ClaimsPrincipal(identity)
};
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Options, Backchannel, tokens, payload);
var identifier = GoogleHelper.GetId(payload);
if (!string.IsNullOrEmpty(identifier))

View File

@ -2,14 +2,18 @@
// 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.Http.Authentication;
namespace Microsoft.AspNet.Authentication.JwtBearer
{
public class JwtBearerChallengeContext : BaseJwtBearerContext
{
public JwtBearerChallengeContext(HttpContext context, JwtBearerOptions options)
public JwtBearerChallengeContext(HttpContext context, JwtBearerOptions options, AuthenticationProperties properties)
: base(context, options)
{
Properties = properties;
}
public AuthenticationProperties Properties { get; }
}
}

View File

@ -203,7 +203,7 @@ namespace Microsoft.AspNet.Authentication.JwtBearer
protected override async Task<bool> HandleUnauthorizedAsync(ChallengeContext context)
{
var eventContext = new JwtBearerChallengeContext(Context, Options);
var eventContext = new JwtBearerChallengeContext(Context, Options, new AuthenticationProperties(context.Properties));
await Options.Events.Challenge(eventContext);
if (eventContext.HandledResponse)
{

View File

@ -27,13 +27,8 @@ namespace Microsoft.AspNet.Authentication.MicrosoftAccount
response.EnsureSuccessStatusCode();
var payload = JObject.Parse(await response.Content.ReadAsStringAsync());
var context = new OAuthCreatingTicketContext(Context, Options, Backchannel, tokens, payload)
{
Properties = properties,
Principal = new ClaimsPrincipal(identity)
};
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Options, Backchannel, tokens, payload);
var identifier = MicrosoftAccountHelper.GetId(payload);
if (!string.IsNullOrEmpty(identifier))
{

View File

@ -19,28 +19,36 @@ namespace Microsoft.AspNet.Authentication.OAuth
/// <summary>
/// Initializes a new <see cref="OAuthCreatingTicketContext"/>.
/// </summary>
/// <param name="principal">The <see cref="ClaimsPrincipal"/> representing the user.</param>
/// <param name="properties">Property bag for common authentication properties.</param>
/// <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>
public OAuthCreatingTicketContext(
ClaimsPrincipal principal,
AuthenticationProperties properties,
HttpContext context,
OAuthOptions options,
HttpClient backchannel,
OAuthTokenResponse tokens)
: this(context, options, backchannel, tokens, user: new JObject())
: this(principal, properties, context, options, backchannel, tokens, user: new JObject())
{
}
/// <summary>
/// Initializes a new <see cref="OAuthCreatingTicketContext"/>.
/// </summary>
/// <param name="principal">The <see cref="ClaimsPrincipal"/> representing the user.</param>
/// <param name="properties">Property bag for common authentication properties.</param>
/// <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 OAuthCreatingTicketContext(
ClaimsPrincipal principal,
AuthenticationProperties properties,
HttpContext context,
OAuthOptions options,
HttpClient backchannel,
@ -77,6 +85,8 @@ namespace Microsoft.AspNet.Authentication.OAuth
Backchannel = backchannel;
User = user;
Options = options;
Principal = principal;
Properties = properties;
}
public OAuthOptions Options { get; }

View File

@ -155,11 +155,7 @@ namespace Microsoft.AspNet.Authentication.OAuth
protected virtual async Task<AuthenticationTicket> CreateTicketAsync(ClaimsIdentity identity, AuthenticationProperties properties, OAuthTokenResponse tokens)
{
var context = new OAuthCreatingTicketContext(Context, Options, Backchannel, tokens)
{
Principal = new ClaimsPrincipal(identity),
Properties = properties
};
var context = new OAuthCreatingTicketContext(new ClaimsPrincipal(identity), properties, Context, Options, Backchannel, tokens);
await Options.Events.CreatingTicket(context);

View File

@ -2,17 +2,21 @@
// 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.Http.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
public class AuthenticationValidatedContext : BaseOpenIdConnectContext
{
public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options)
public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
: base(context, options)
{
Properties = properties;
}
public AuthenticationProperties Properties { get; }
public OpenIdConnectMessage TokenEndpointResponse { get; set; }
}
}

View File

@ -4,7 +4,7 @@
using System.Diagnostics.CodeAnalysis;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNet.Http;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.AspNet.Http.Authentication;
namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
@ -16,11 +16,14 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary>
/// Creates a <see cref="AuthorizationCodeReceivedContext"/>
/// </summary>
public AuthorizationCodeReceivedContext(HttpContext context, OpenIdConnectOptions options)
public AuthorizationCodeReceivedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
: base(context, options)
{
{
Properties = properties;
}
public AuthenticationProperties Properties { get; set; }
/// <summary>
/// Gets or sets the 'code'.
/// </summary>

View File

@ -9,11 +9,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
{
public class AuthorizationResponseReceivedContext : BaseOpenIdConnectContext
{
public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options)
public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
: base(context, options)
{
Properties = properties;
}
public AuthenticationProperties Properties { get; set; }
public AuthenticationProperties Properties { get; }
}
}

View File

@ -2,6 +2,7 @@
// 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.Http.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect
@ -12,9 +13,12 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// </summary>
public class RedirectContext : BaseOpenIdConnectContext
{
public RedirectContext(HttpContext context, OpenIdConnectOptions options)
public RedirectContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
: base(context, options)
{
Properties = properties;
}
public AuthenticationProperties Properties { get; }
}
}

View File

@ -1,4 +1,5 @@
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Http.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
namespace Microsoft.AspNet.Authentication.OpenIdConnect
@ -11,11 +12,14 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
/// <summary>
/// Creates a <see cref="TokenResponseReceivedContext"/>
/// </summary>
public TokenResponseReceivedContext(HttpContext context, OpenIdConnectOptions options)
public TokenResponseReceivedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties)
: base(context, options)
{
Properties = properties;
}
public AuthenticationProperties Properties { get; }
/// <summary>
/// Gets or sets the <see cref="OpenIdConnectMessage"/> that contains the tokens received after redeeming the code at the token endpoint.
/// </summary>

View File

@ -99,7 +99,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
message.IdTokenHint = principal?.FindFirst(OpenIdConnectParameterNames.IdToken)?.Value;
}
var redirectContext = new RedirectContext(Context, Options)
var redirectContext = new RedirectContext(Context, Options, properties)
{
ProtocolMessage = message
};
@ -215,7 +215,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
GenerateCorrelationId(properties);
var redirectContext = new RedirectContext(Context, Options)
var redirectContext = new RedirectContext(Context, Options, properties)
{
ProtocolMessage = message
};
@ -379,10 +379,9 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
}
Logger.LogTrace(15, "Authorization response received.");
var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options)
var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options, properties)
{
ProtocolMessage = message,
Properties = properties
ProtocolMessage = message
};
await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext);
if (authorizationResponseReceivedContext.HandledResponse)
@ -469,7 +468,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
var tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri);
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse);
var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse, properties);
if (authorizationCodeRedeemedContext.HandledResponse)
{
return AuthenticateResult.Success(authorizationCodeRedeemedContext.AuthenticationTicket);
@ -502,7 +501,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Nonce = nonce
});
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse);
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, properties, tokenEndpointResponse);
if (authenticationValidatedContext.HandledResponse)
{
return AuthenticateResult.Success(authenticationValidatedContext.AuthenticationTicket);
@ -551,7 +550,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Nonce = nonce
});
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse: null);
var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, properties, tokenEndpointResponse: null);
if (authenticationValidatedContext.HandledResponse)
{
return AuthenticateResult.Success(authenticationValidatedContext.AuthenticationTicket);
@ -950,7 +949,7 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
Logger.LogTrace(32, "AuthorizationCode received: '{0}'", message.Code);
var authorizationCodeReceivedContext = new AuthorizationCodeReceivedContext(Context, Options)
var authorizationCodeReceivedContext = new AuthorizationCodeReceivedContext(Context, Options, properties)
{
Code = message.Code,
ProtocolMessage = message,
@ -972,10 +971,10 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
return authorizationCodeReceivedContext;
}
private async Task<TokenResponseReceivedContext> RunTokenResponseReceivedEventAsync(OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse)
private async Task<TokenResponseReceivedContext> RunTokenResponseReceivedEventAsync(OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties)
{
Logger.LogTrace(35, "Token response received.");
var tokenResponseReceivedContext = new TokenResponseReceivedContext(Context, Options)
var tokenResponseReceivedContext = new TokenResponseReceivedContext(Context, Options, properties)
{
ProtocolMessage = message,
TokenEndpointResponse = tokenEndpointResponse
@ -993,9 +992,9 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect
return tokenResponseReceivedContext;
}
private async Task<AuthenticationValidatedContext> RunAuthenticationValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket, OpenIdConnectMessage tokenEndpointResponse)
private async Task<AuthenticationValidatedContext> RunAuthenticationValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket, AuthenticationProperties properties, OpenIdConnectMessage tokenEndpointResponse)
{
var authenticationValidatedContext = new AuthenticationValidatedContext(Context, Options)
var authenticationValidatedContext = new AuthenticationValidatedContext(Context, Options, properties)
{
AuthenticationTicket = ticket,
ProtocolMessage = message,

View File

@ -533,6 +533,54 @@ namespace Microsoft.AspNet.Authentication.Cookies
Assert.Null(FindClaimValue(transaction5, ClaimTypes.Name));
}
[Fact]
public async Task CookieValidatorOnlyCalledOnce()
{
var clock = new TestClock();
var server = CreateServer(options =>
{
options.SystemClock = clock;
options.ExpireTimeSpan = TimeSpan.FromMinutes(10);
options.SlidingExpiration = false;
options.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = ctx =>
{
ctx.ShouldRenew = true;
return Task.FromResult(0);
}
};
},
context =>
context.Authentication.SignInAsync("Cookies",
new ClaimsPrincipal(new ClaimsIdentity(new GenericIdentity("Alice", "Cookies")))));
var transaction1 = await SendAsync(server, "http://example.com/testpath");
var transaction2 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
Assert.NotNull(transaction2.SetCookie);
Assert.Equal("Alice", FindClaimValue(transaction2, ClaimTypes.Name));
clock.Add(TimeSpan.FromMinutes(5));
var transaction3 = await SendAsync(server, "http://example.com/me/Cookies", transaction2.CookieNameValue);
Assert.NotNull(transaction3.SetCookie);
Assert.Equal("Alice", FindClaimValue(transaction3, ClaimTypes.Name));
clock.Add(TimeSpan.FromMinutes(6));
var transaction4 = await SendAsync(server, "http://example.com/me/Cookies", transaction1.CookieNameValue);
Assert.Null(transaction4.SetCookie);
Assert.Null(FindClaimValue(transaction4, ClaimTypes.Name));
clock.Add(TimeSpan.FromMinutes(5));
var transaction5 = await SendAsync(server, "http://example.com/me/Cookies", transaction2.CookieNameValue);
Assert.Null(transaction5.SetCookie);
Assert.Null(FindClaimValue(transaction5, ClaimTypes.Name));
}
[Fact]
public async Task CookieExpirationCanBeOverridenInEvent()
{