From 3f596108aac3d8fc7fb40d39e19a7f897a90c198 Mon Sep 17 00:00:00 2001 From: Chris R Date: Tue, 15 Mar 2016 15:19:25 -0700 Subject: [PATCH] #690 OIDC & JWT event refactoring. --- .../Properties/launchSettings.json | 2 +- samples/JwtBearerSample/Startup.cs | 6 +- samples/JwtBearerSample/project.json | 6 +- .../Properties/launchSettings.json | 2 +- .../OpenIdConnect.AzureAdSample/Startup.cs | 5 +- .../OpenIdConnect.AzureAdSample/project.json | 3 + .../Properties/launchSettings.json | 2 +- samples/OpenIdConnectSample/Startup.cs | 5 +- samples/OpenIdConnectSample/project.json | 5 +- .../Events/IJwtBearerEvents.cs | 9 +- .../Events/JwtBearerEvents.cs | 15 +- ...enContext.cs => MessageReceivedContext.cs} | 4 +- .../Events/ReceivedTokenContext.cs | 18 -- .../Events/TokenValidatedContext.cs | 7 +- .../JwtBearerHandler.cs | 63 ++---- .../JwtBearerOptions.cs | 2 + .../AuthorizationResponseReceivedContext.cs | 20 -- .../Events/IOpenIdConnectEvents.cs | 19 +- .../Events/MessageReceivedContext.cs | 3 + .../Events/OpenIdConnectEvents.cs | 29 +-- ...tedContext.cs => TokenValidatedContext.cs} | 18 +- .../LoggingExtensions.cs | 110 ++++----- .../OpenIdConnectHandler.cs | 210 ++++++++---------- .../Events/BaseControlContext.cs | 16 ++ .../JwtBearer/JwtBearerMiddlewareTests.cs | 181 +++------------ .../OpenIdConnectMiddlewareTests.cs | 8 +- 26 files changed, 278 insertions(+), 490 deletions(-) rename src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/{ReceivingTokenContext.cs => MessageReceivedContext.cs} (79%) delete mode 100644 src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivedTokenContext.cs delete mode 100644 src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs rename src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/{AuthenticationValidatedContext.cs => TokenValidatedContext.cs} (52%) diff --git a/samples/JwtBearerSample/Properties/launchSettings.json b/samples/JwtBearerSample/Properties/launchSettings.json index af63bba52f..49cbac543a 100644 --- a/samples/JwtBearerSample/Properties/launchSettings.json +++ b/samples/JwtBearerSample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { - "ASPNET_ENVIRONMENT": "Development" + "ASPNETCORE_ENVIRONMENT": "Development" } }, "web": { diff --git a/samples/JwtBearerSample/Startup.cs b/samples/JwtBearerSample/Startup.cs index 1df7e11a39..78e6d0f406 100644 --- a/samples/JwtBearerSample/Startup.cs +++ b/samples/JwtBearerSample/Startup.cs @@ -61,8 +61,6 @@ namespace JwtBearerSample app.UseJwtBearerAuthentication(new JwtBearerOptions { - AutomaticAuthenticate = true, - AutomaticChallenge = true, // You also need to update /wwwroot/app/scripts/app.js Authority = Configuration["jwt:authority"], Audience = Configuration["jwt:audience"] @@ -74,14 +72,14 @@ namespace JwtBearerSample // Use this if options.AutomaticAuthenticate = false // var user = await context.Authentication.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); - var user = context.User; // We can do this because of options.AutomaticAuthenticate = true; above. + var user = context.User; // We can do this because of options.AutomaticAuthenticate = true; if (user?.Identity?.IsAuthenticated ?? false) { await next(); } else { - // We can do this because of options.AutomaticChallenge = true; above + // We can do this because of options.AutomaticChallenge = true; await context.Authentication.ChallengeAsync(); } }); diff --git a/samples/JwtBearerSample/project.json b/samples/JwtBearerSample/project.json index 0f6a6c9df5..a271c2b5c7 100644 --- a/samples/JwtBearerSample/project.json +++ b/samples/JwtBearerSample/project.json @@ -15,7 +15,7 @@ "web": "JwtBearerSample" }, "frameworks": { - "dnx451": {}, + "dnx451": { }, "netstandardapp1.5": { "imports": [ "dnxcore50" @@ -30,5 +30,9 @@ "**.user", "**.vspscc" ], + "content": [ + "project.json", + "wwwroot" + ], "userSecretsId": "aspnet5-JwtBearerSample-20151210102827" } \ No newline at end of file diff --git a/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json b/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json index 22d7eec72e..49cbac543a 100644 --- a/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json +++ b/samples/OpenIdConnect.AzureAdSample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { - "Hosting:Environment": "Development" + "ASPNETCORE_ENVIRONMENT": "Development" } }, "web": { diff --git a/samples/OpenIdConnect.AzureAdSample/Startup.cs b/samples/OpenIdConnect.AzureAdSample/Startup.cs index f2f9489bf5..3bbae57b4a 100644 --- a/samples/OpenIdConnect.AzureAdSample/Startup.cs +++ b/samples/OpenIdConnect.AzureAdSample/Startup.cs @@ -64,10 +64,7 @@ namespace OpenIdConnect.AzureAdSample app.UseIISPlatformHandler(); - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - AutomaticAuthenticate = true - }); + app.UseCookieAuthentication(new CookieAuthenticationOptions()); var clientId = Configuration["oidc:clientid"]; var clientSecret = Configuration["oidc:clientsecret"]; diff --git a/samples/OpenIdConnect.AzureAdSample/project.json b/samples/OpenIdConnect.AzureAdSample/project.json index a25fc16613..fd74610190 100644 --- a/samples/OpenIdConnect.AzureAdSample/project.json +++ b/samples/OpenIdConnect.AzureAdSample/project.json @@ -18,5 +18,8 @@ "commands": { "web": "OpenIdConnect.AzureAdSample" }, + "content": [ + "project.json" + ], "userSecretsId": "aspnet5-OpenIdConnectSample-20151210110318" } \ No newline at end of file diff --git a/samples/OpenIdConnectSample/Properties/launchSettings.json b/samples/OpenIdConnectSample/Properties/launchSettings.json index c75dba9f49..5a0163016a 100644 --- a/samples/OpenIdConnectSample/Properties/launchSettings.json +++ b/samples/OpenIdConnectSample/Properties/launchSettings.json @@ -12,7 +12,7 @@ "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { - "ASPNET_ENV": "Development" + "ASPNETCORE_ENVIRONMENT": "Development" } }, "web": { diff --git a/samples/OpenIdConnectSample/Startup.cs b/samples/OpenIdConnectSample/Startup.cs index ff156a77fb..bad559aa22 100644 --- a/samples/OpenIdConnectSample/Startup.cs +++ b/samples/OpenIdConnectSample/Startup.cs @@ -59,10 +59,7 @@ namespace OpenIdConnectSample app.UseIISPlatformHandler(); - app.UseCookieAuthentication(new CookieAuthenticationOptions - { - AutomaticAuthenticate = true - }); + app.UseCookieAuthentication(new CookieAuthenticationOptions()); app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions { diff --git a/samples/OpenIdConnectSample/project.json b/samples/OpenIdConnectSample/project.json index 12d88dfed1..4fd829a22c 100644 --- a/samples/OpenIdConnectSample/project.json +++ b/samples/OpenIdConnectSample/project.json @@ -9,7 +9,7 @@ "Microsoft.NETCore.Platforms": "1.0.1-*" }, "frameworks": { - "dnx451": {}, + "dnx451": { }, "netstandardapp1.5": { "imports": [ "dnxcore50" @@ -22,5 +22,8 @@ "commands": { "web": "OpenIdConnectSample" }, + "content": [ + "project.json" + ], "userSecretsId": "aspnet5-OpenIdConnectSample-20151210110318" } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/IJwtBearerEvents.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/IJwtBearerEvents.cs index 2e023db130..a7b8aeb552 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/IJwtBearerEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/IJwtBearerEvents.cs @@ -18,17 +18,12 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer /// /// Invoked when a protocol message is first received. /// - Task ReceivingToken(ReceivingTokenContext context); - - /// - /// Invoked with the security token that has been extracted from the protocol message. - /// - Task ReceivedToken(ReceivedTokenContext context); + Task MessageReceived(MessageReceivedContext context); /// /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. /// - Task ValidatedToken(ValidatedTokenContext context); + Task TokenValidated(TokenValidatedContext context); /// /// Invoked to apply a challenge sent back to the caller. diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerEvents.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerEvents.cs index a14f238078..38a877f668 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/JwtBearerEvents.cs @@ -19,17 +19,12 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer /// /// Invoked when a protocol message is first received. /// - public Func OnReceivingToken { get; set; } = context => Task.FromResult(0); - - /// - /// Invoked with the security token that has been extracted from the protocol message. - /// - public Func OnReceivedToken { get; set; } = context => Task.FromResult(0); + public Func OnMessageReceived { get; set; } = context => Task.FromResult(0); /// /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. /// - public Func OnValidatedToken { get; set; } = context => Task.FromResult(0); + public Func OnTokenValidated { get; set; } = context => Task.FromResult(0); /// /// Invoked before a challenge is sent back to the caller. @@ -38,11 +33,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context); - public virtual Task ReceivingToken(ReceivingTokenContext context) => OnReceivingToken(context); + public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context); - public virtual Task ReceivedToken(ReceivedTokenContext context) => OnReceivedToken(context); - - public virtual Task ValidatedToken(ValidatedTokenContext context) => OnValidatedToken(context); + public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context); public virtual Task Challenge(JwtBearerChallengeContext context) => OnChallenge(context); } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivingTokenContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs similarity index 79% rename from src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivingTokenContext.cs rename to src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs index e93ad824ad..a23f8356da 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivingTokenContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/MessageReceivedContext.cs @@ -6,9 +6,9 @@ using Microsoft.AspNetCore.Http; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class ReceivingTokenContext : BaseJwtBearerContext + public class MessageReceivedContext : BaseJwtBearerContext { - public ReceivingTokenContext(HttpContext context, JwtBearerOptions options) + public MessageReceivedContext(HttpContext context, JwtBearerOptions options) : base(context, options) { } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivedTokenContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivedTokenContext.cs deleted file mode 100644 index e38c49cf15..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/ReceivedTokenContext.cs +++ /dev/null @@ -1,18 +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.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; - -namespace Microsoft.AspNetCore.Authentication.JwtBearer -{ - public class ReceivedTokenContext : BaseJwtBearerContext - { - public ReceivedTokenContext(HttpContext context, JwtBearerOptions options) - : base(context, options) - { - } - - public string Token { get; set; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs index 3a1dad812f..d6de5ca873 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/Events/TokenValidatedContext.cs @@ -3,14 +3,17 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.IdentityModel.Tokens; namespace Microsoft.AspNetCore.Authentication.JwtBearer { - public class ValidatedTokenContext : BaseJwtBearerContext + public class TokenValidatedContext : BaseJwtBearerContext { - public ValidatedTokenContext(HttpContext context, JwtBearerOptions options) + public TokenValidatedContext(HttpContext context, JwtBearerOptions options) : base(context, options) { } + + public SecurityToken SecurityToken { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs index b1d2d702eb..40a0b2efd7 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs @@ -28,24 +28,21 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer protected override async Task HandleAuthenticateAsync() { string token = null; + AuthenticateResult result = null; try { // Give application opportunity to find from a different location, adjust, or reject token - var receivingTokenContext = new ReceivingTokenContext(Context, Options); + var messageReceivedContext = new MessageReceivedContext(Context, Options); // event can set the token - await Options.Events.ReceivingToken(receivingTokenContext); - if (receivingTokenContext.HandledResponse) + await Options.Events.MessageReceived(messageReceivedContext); + if (messageReceivedContext.CheckEventResult(out result)) { - return AuthenticateResult.Success(receivingTokenContext.Ticket); - } - if (receivingTokenContext.Skipped) - { - return AuthenticateResult.Skip(); + return result; } // If application retrieved token from somewhere else, use that. - token = receivingTokenContext.Token; + token = messageReceivedContext.Token; if (string.IsNullOrEmpty(token)) { @@ -69,22 +66,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } } - // notify user token was received - var receivedTokenContext = new ReceivedTokenContext(Context, Options) - { - Token = token, - }; - - await Options.Events.ReceivedToken(receivedTokenContext); - if (receivedTokenContext.HandledResponse) - { - return AuthenticateResult.Success(receivedTokenContext.Ticket); - } - if (receivedTokenContext.Skipped) - { - return AuthenticateResult.Skip(); - } - if (_configuration == null && Options.ConfigurationManager != null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); @@ -138,20 +119,18 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Logger.TokenValidationSucceeded(); var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Options.AuthenticationScheme); - var validatedTokenContext = new ValidatedTokenContext(Context, Options) + var tokenValidatedContext = new TokenValidatedContext(Context, Options) { - Ticket = ticket + Ticket = ticket, + SecurityToken = validatedToken, }; - await Options.Events.ValidatedToken(validatedTokenContext); - if (validatedTokenContext.HandledResponse) + await Options.Events.TokenValidated(tokenValidatedContext); + if (tokenValidatedContext.CheckEventResult(out result)) { - return AuthenticateResult.Success(validatedTokenContext.Ticket); - } - if (validatedTokenContext.Skipped) - { - return AuthenticateResult.Skip(); + return result; } + ticket = tokenValidatedContext.Ticket; if (Options.SaveToken) { @@ -173,13 +152,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }; await Options.Events.AuthenticationFailed(authenticationFailedContext); - if (authenticationFailedContext.HandledResponse) + if (authenticationFailedContext.CheckEventResult(out result)) { - return AuthenticateResult.Success(authenticationFailedContext.Ticket); - } - if (authenticationFailedContext.Skipped) - { - return AuthenticateResult.Skip(); + return result; } return AuthenticateResult.Fail(authenticationFailedContext.Exception); @@ -197,13 +172,9 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }; await Options.Events.AuthenticationFailed(authenticationFailedContext); - if (authenticationFailedContext.HandledResponse) + if (authenticationFailedContext.CheckEventResult(out result)) { - return AuthenticateResult.Success(authenticationFailedContext.Ticket); - } - if (authenticationFailedContext.Skipped) - { - return AuthenticateResult.Skip(); + return result; } throw; diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs index c350c38baf..837928e777 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerOptions.cs @@ -25,6 +25,8 @@ namespace Microsoft.AspNetCore.Builder public JwtBearerOptions() : base() { AuthenticationScheme = JwtBearerDefaults.AuthenticationScheme; + AutomaticAuthenticate = true; + AutomaticChallenge = true; } /// diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs deleted file mode 100644 index 7d17d3cf80..0000000000 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs +++ /dev/null @@ -1,20 +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.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Authentication; - -namespace Microsoft.AspNetCore.Authentication.OpenIdConnect -{ - public class AuthorizationResponseReceivedContext : BaseOpenIdConnectContext - { - public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties) - : base(context, options) - { - Properties = properties; - } - - public AuthenticationProperties Properties { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs index da956acf82..57600cee8d 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs @@ -15,21 +15,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// Task AuthenticationFailed(AuthenticationFailedContext context); - /// - /// Invoked after the id token has passed validation and a ClaimsIdentity has been generated. - /// - Task AuthenticationValidated(AuthenticationValidatedContext context); - /// /// Invoked after security token validation if an authorization code is present in the protocol message. /// Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context); - /// - /// Invoked when an authorization response is received. - /// - Task AuthorizationResponseReceived(AuthorizationResponseReceivedContext context); - /// /// Invoked when a protocol message is first received. /// @@ -38,18 +28,23 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// Invoked before redirecting to the identity provider to authenticate. /// - Task RedirectToAuthenticationEndpoint(RedirectContext context); + Task RedirectToIdentityProvider(RedirectContext context); /// /// Invoked before redirecting to the identity provider to sign out. /// - Task RedirectToEndSessionEndpoint(RedirectContext context); + Task RedirectToIdentityProviderForSignOut(RedirectContext context); /// /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. /// Task TokenResponseReceived(TokenResponseReceivedContext context); + /// + /// Invoked when an IdToken has been validated and produced an AuthenticationTicket. + /// + Task TokenValidated(TokenValidatedContext context); + /// /// Invoked when user information is retrieved from the UserInfoEndpoint. /// diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs index d535f35f92..b2554969c1 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/MessageReceivedContext.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Authentication; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { @@ -17,5 +18,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// Bearer Token. This will give application an opportunity to retrieve token from an alternation location. /// public string Token { get; set; } + + public AuthenticationProperties Properties { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs index 249342eecf..9893b72072 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs @@ -16,21 +16,11 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// public Func OnAuthenticationFailed { get; set; } = context => Task.FromResult(0); - /// - /// Invoked after the id token has passed validation and a ClaimsIdentity has been generated. - /// - public Func OnAuthenticationValidated { get; set; } = context => Task.FromResult(0); - /// /// Invoked after security token validation if an authorization code is present in the protocol message. /// public Func OnAuthorizationCodeReceived { get; set; } = context => Task.FromResult(0); - /// - /// Invoked when an authorization response is received. - /// - public Func OnAuthorizationResponseReceived { get; set; } = context => Task.FromResult(0); - /// /// Invoked when a protocol message is first received. /// @@ -39,18 +29,23 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// /// Invoked before redirecting to the identity provider to authenticate. /// - public Func OnRedirectToAuthenticationEndpoint { get; set; } = context => Task.FromResult(0); + public Func OnRedirectToIdentityProvider { get; set; } = context => Task.FromResult(0); /// /// Invoked before redirecting to the identity provider to sign out. /// - public Func OnRedirectToEndSessionEndpoint { get; set; } = context => Task.FromResult(0); + public Func OnRedirectToIdentityProviderForSignOut { get; set; } = context => Task.FromResult(0); /// /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. /// public Func OnTokenResponseReceived { get; set; } = context => Task.FromResult(0); + /// + /// Invoked when an IdToken has been validated and produced an AuthenticationTicket. + /// + public Func OnTokenValidated { get; set; } = context => Task.FromResult(0); + /// /// Invoked when user information is retrieved from the UserInfoEndpoint. /// @@ -58,20 +53,18 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context); - public virtual Task AuthenticationValidated(AuthenticationValidatedContext context) => OnAuthenticationValidated(context); - public virtual Task AuthorizationCodeReceived(AuthorizationCodeReceivedContext context) => OnAuthorizationCodeReceived(context); - public virtual Task AuthorizationResponseReceived(AuthorizationResponseReceivedContext context) => OnAuthorizationResponseReceived(context); - public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context); - public virtual Task RedirectToAuthenticationEndpoint(RedirectContext context) => OnRedirectToAuthenticationEndpoint(context); + public virtual Task RedirectToIdentityProvider(RedirectContext context) => OnRedirectToIdentityProvider(context); - public virtual Task RedirectToEndSessionEndpoint(RedirectContext context) => OnRedirectToEndSessionEndpoint(context); + public virtual Task RedirectToIdentityProviderForSignOut(RedirectContext context) => OnRedirectToIdentityProviderForSignOut(context); public virtual Task TokenResponseReceived(TokenResponseReceivedContext context) => OnTokenResponseReceived(context); + public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context); + public virtual Task UserInformationReceived(UserInformationReceivedContext context) => OnUserInformationReceived(context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationValidatedContext.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs similarity index 52% rename from src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationValidatedContext.cs rename to src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs index 4e19796457..130a4d9873 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/AuthenticationValidatedContext.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/Events/TokenValidatedContext.cs @@ -1,6 +1,8 @@ // 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.IdentityModel.Tokens.Jwt; +using System.Net.Http; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Authentication; @@ -8,16 +10,22 @@ using Microsoft.IdentityModel.Protocols.OpenIdConnect; namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { - public class AuthenticationValidatedContext : BaseOpenIdConnectContext + public class TokenValidatedContext : BaseOpenIdConnectContext { - public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options, AuthenticationProperties properties) + /// + /// Creates a + /// + public TokenValidatedContext(HttpContext context, OpenIdConnectOptions options) : base(context, options) { - Properties = properties; } - public AuthenticationProperties Properties { get; } + public AuthenticationProperties Properties { get; set; } + + public JwtSecurityToken SecurityToken { get; set; } public OpenIdConnectMessage TokenEndpointResponse { get; set; } + + public string Nonce { get; set; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs index 8d45c9918c..ff580ff266 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/LoggingExtensions.cs @@ -7,10 +7,10 @@ namespace Microsoft.Extensions.Logging { internal static class LoggingExtensions { - private static Action _redirectToEndSessionEndpointHandledResponse; - private static Action _redirectToEndSessionEndpointSkipped; - private static Action _redirectToAuthenticationEndpointHandledResponse; - private static Action _redirectToAuthenticationEndpointSkipped; + private static Action _redirectToIdentityProviderForSignOutHandledResponse; + private static Action _redirectToIdentityProviderForSignOutSkipped; + private static Action _redirectToIdentityProviderHandledResponse; + private static Action _redirectToIdentityProviderSkipped; private static Action _updatingConfiguration; private static Action _receivedIdToken; private static Action _redeemingCodeForTokens; @@ -19,20 +19,17 @@ namespace Microsoft.Extensions.Logging private static Action _messageReceived; private static Action _messageReceivedContextHandledResponse; private static Action _messageReceivedContextSkipped; - private static Action _authorizationResponseReceived; private static Action _authorizationCodeReceived; private static Action _configurationManagerRequestRefreshCalled; private static Action _tokenResponseReceived; - private static Action _authorizationResponseReceivedHandledResponse; - private static Action _authorizationResponseReceivedSkipped; + private static Action _tokenValidatedHandledResponse; + private static Action _tokenValidatedSkipped; private static Action _authenticationFailedContextHandledResponse; private static Action _authenticationFailedContextSkipped; private static Action _authorizationCodeReceivedContextHandledResponse; private static Action _authorizationCodeReceivedContextSkipped; - private static Action _authorizationCodeRedeemedContextHandledResponse; - private static Action _authorizationCodeRedeemedContextSkipped; - private static Action _authenticationValidatedHandledResponse; - private static Action _authenticationValidatedtSkipped; + private static Action _tokenResponseReceivedHandledResponse; + private static Action _tokenResponseReceivedSkipped; private static Action _userInformationReceived; private static Action _userInformationReceivedHandledResponse; private static Action _userInformationReceivedSkipped; @@ -54,14 +51,14 @@ namespace Microsoft.Extensions.Logging static LoggingExtensions() { // Final - _redirectToEndSessionEndpointHandledResponse = LoggerMessage.Define( + _redirectToIdentityProviderForSignOutHandledResponse = LoggerMessage.Define( eventId: 1, logLevel: LogLevel.Debug, - formatString: "RedirectToEndSessionEndpoint.HandledResponse"); - _redirectToEndSessionEndpointSkipped = LoggerMessage.Define( + formatString: "RedirectToIdentityProviderForSignOut.HandledResponse"); + _redirectToIdentityProviderForSignOutSkipped = LoggerMessage.Define( eventId: 2, logLevel: LogLevel.Debug, - formatString: "RedirectToEndSessionEndpoint.Skipped"); + formatString: "RedirectToIdentityProviderForSignOut.Skipped"); _invalidLogoutQueryStringRedirectUrl = LoggerMessage.Define( eventId: 3, logLevel: LogLevel.Warning, @@ -74,14 +71,14 @@ namespace Microsoft.Extensions.Logging eventId: 5, logLevel: LogLevel.Trace, formatString: "Using properties.RedirectUri for 'local redirect' post authentication: '{RedirectUri}'."); - _redirectToAuthenticationEndpointHandledResponse = LoggerMessage.Define( + _redirectToIdentityProviderHandledResponse = LoggerMessage.Define( eventId: 6, logLevel: LogLevel.Debug, - formatString: "RedirectToAuthenticationEndpoint.HandledResponse"); - _redirectToAuthenticationEndpointSkipped = LoggerMessage.Define( + formatString: "RedirectToIdentityProvider.HandledResponse"); + _redirectToIdentityProviderSkipped = LoggerMessage.Define( eventId: 7, logLevel: LogLevel.Debug, - formatString: "RedirectToAuthenticationEndpoint.Skipped"); + formatString: "RedirectToIdentityProvider.Skipped"); _invalidAuthenticationRequestUrl = LoggerMessage.Define( eventId: 8, logLevel: LogLevel.Warning, @@ -106,18 +103,14 @@ namespace Microsoft.Extensions.Logging eventId: 13, logLevel: LogLevel.Debug, formatString: "Updating configuration"); - _authorizationResponseReceived = LoggerMessage.Define( - eventId: 14, - logLevel: LogLevel.Trace, - formatString: "Authorization response received."); - _authorizationResponseReceivedHandledResponse = LoggerMessage.Define( + _tokenValidatedHandledResponse = LoggerMessage.Define( eventId: 15, logLevel: LogLevel.Debug, - formatString: "AuthorizationResponseReceived.HandledResponse"); - _authorizationResponseReceivedSkipped = LoggerMessage.Define( + formatString: "TokenValidated.HandledResponse"); + _tokenValidatedSkipped = LoggerMessage.Define( eventId: 16, logLevel: LogLevel.Debug, - formatString: "AuthorizationResponseReceived.Skipped"); + formatString: "TokenValidated.Skipped"); _exceptionProcessingMessage = LoggerMessage.Define( eventId: 17, logLevel: LogLevel.Error, @@ -174,22 +167,14 @@ namespace Microsoft.Extensions.Logging eventId: 30, logLevel: LogLevel.Trace, formatString: "Token response received."); - _authorizationCodeRedeemedContextHandledResponse = LoggerMessage.Define( + _tokenResponseReceivedHandledResponse = LoggerMessage.Define( eventId: 31, logLevel: LogLevel.Debug, - formatString: "AuthorizationCodeRedeemedContext.HandledResponse"); - _authorizationCodeRedeemedContextSkipped = LoggerMessage.Define( + formatString: "TokenResponseReceived.HandledResponse"); + _tokenResponseReceivedSkipped = LoggerMessage.Define( eventId: 32, logLevel: LogLevel.Debug, - formatString: "AuthorizationCodeRedeemedContext.Skipped"); - _authenticationValidatedHandledResponse = LoggerMessage.Define( - eventId: 33, - logLevel: LogLevel.Debug, - formatString: "AuthenticationFailedContext.HandledResponse"); - _authenticationValidatedtSkipped = LoggerMessage.Define( - eventId: 34, - logLevel: LogLevel.Debug, - formatString: "AuthenticationFailedContext.Skipped"); + formatString: "TokenResponseReceived.Skipped"); _userInformationReceived = LoggerMessage.Define( eventId: 35, logLevel: LogLevel.Trace, @@ -258,19 +243,14 @@ namespace Microsoft.Extensions.Logging _redeemingCodeForTokens(logger, null); } - public static void AuthorizationResponseReceived(this ILogger logger) + public static void TokenValidatedHandledResponse(this ILogger logger) { - _authorizationResponseReceived(logger, null); + _tokenValidatedHandledResponse(logger, null); } - public static void AuthorizationResponseReceivedHandledResponse(this ILogger logger) + public static void TokenValidatedSkipped(this ILogger logger) { - _authorizationResponseReceivedHandledResponse(logger, null); - } - - public static void AuthorizationResponseReceivedSkipped(this ILogger logger) - { - _authorizationResponseReceivedSkipped(logger, null); + _tokenValidatedSkipped(logger, null); } public static void AuthorizationCodeReceivedContextHandledResponse(this ILogger logger) @@ -283,24 +263,14 @@ namespace Microsoft.Extensions.Logging _authorizationCodeReceivedContextSkipped(logger, null); } - public static void AuthorizationCodeRedeemedContextHandledResponse(this ILogger logger) + public static void TokenResponseReceivedHandledResponse(this ILogger logger) { - _authorizationCodeRedeemedContextHandledResponse(logger, null); + _tokenResponseReceivedHandledResponse(logger, null); } - public static void AuthorizationCodeRedeemedContextSkipped(this ILogger logger) + public static void TokenResponseReceivedSkipped(this ILogger logger) { - _authorizationCodeRedeemedContextSkipped(logger, null); - } - - public static void AuthenticationValidatedHandledResponse(this ILogger logger) - { - _authenticationValidatedHandledResponse(logger, null); - } - - public static void AuthenticationValidatedSkipped(this ILogger logger) - { - _authenticationValidatedtSkipped(logger, null); + _tokenResponseReceivedSkipped(logger, null); } public static void AuthenticationFailedContextHandledResponse(this ILogger logger) @@ -328,24 +298,24 @@ namespace Microsoft.Extensions.Logging _messageReceivedContextSkipped(logger, null); } - public static void RedirectToEndSessionEndpointHandledResponse(this ILogger logger) + public static void RedirectToIdentityProviderForSignOutHandledResponse(this ILogger logger) { - _redirectToEndSessionEndpointHandledResponse(logger, null); + _redirectToIdentityProviderForSignOutHandledResponse(logger, null); } - public static void RedirectToEndSessionEndpointSkipped(this ILogger logger) + public static void RedirectToIdentityProviderForSignOutSkipped(this ILogger logger) { - _redirectToEndSessionEndpointSkipped(logger, null); + _redirectToIdentityProviderForSignOutSkipped(logger, null); } - public static void RedirectToAuthenticationEndpointHandledResponse(this ILogger logger) + public static void RedirectToIdentityProviderHandledResponse(this ILogger logger) { - _redirectToAuthenticationEndpointHandledResponse(logger, null); + _redirectToIdentityProviderHandledResponse(logger, null); } - public static void RedirectToAuthenticationEndpointSkipped(this ILogger logger) + public static void RedirectToIdentityProviderSkipped(this ILogger logger) { - _redirectToAuthenticationEndpointSkipped(logger, null); + _redirectToIdentityProviderSkipped(logger, null); } public static void UserInformationReceivedHandledResponse(this ILogger logger) diff --git a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 5c534696b8..f85ba9c146 100644 --- a/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -111,15 +111,15 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect ProtocolMessage = message }; - await Options.Events.RedirectToEndSessionEndpoint(redirectContext); + await Options.Events.RedirectToIdentityProviderForSignOut(redirectContext); if (redirectContext.HandledResponse) { - Logger.RedirectToEndSessionEndpointHandledResponse(); + Logger.RedirectToIdentityProviderForSignOutHandledResponse(); return; } else if (redirectContext.Skipped) { - Logger.RedirectToEndSessionEndpointSkipped(); + Logger.RedirectToIdentityProviderForSignOutSkipped(); return; } @@ -169,7 +169,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect /// Responds to a 401 Challenge. Sends an OpenIdConnect message to the 'identity authority' to obtain an identity. /// /// - /// Uses log id's OIDCH-0026 - OIDCH-0050, next num: 37 protected override async Task HandleUnauthorizedAsync(ChallengeContext context) { if (context == null) @@ -230,15 +229,15 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect ProtocolMessage = message }; - await Options.Events.RedirectToAuthenticationEndpoint(redirectContext); + await Options.Events.RedirectToIdentityProvider(redirectContext); if (redirectContext.HandledResponse) { - Logger.RedirectToAuthenticationEndpointHandledResponse(); + Logger.RedirectToIdentityProviderHandledResponse(); return true; } else if (redirectContext.Skipped) { - Logger.RedirectToAuthenticationEndpointSkipped(); + Logger.RedirectToIdentityProviderSkipped(); return false; } @@ -350,27 +349,38 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect try { - var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse); - if (CheckEventResult(messageReceivedContext, out result)) + AuthenticationProperties properties = null; + if (!string.IsNullOrEmpty(authorizationResponse.State)) + { + properties = Options.StateDataFormat.Unprotect(authorizationResponse.State); + } + + var messageReceivedContext = await RunMessageReceivedEventAsync(authorizationResponse, properties); + if (messageReceivedContext.CheckEventResult(out result)) { return result; } authorizationResponse = messageReceivedContext.ProtocolMessage; + properties = messageReceivedContext.Properties; - // Fail if state is missing, it's required for the correlation id. - if (string.IsNullOrEmpty(authorizationResponse.State)) + if (properties == null) { - // This wasn't a valid OIDC message, it may not have been intended for us. - Logger.NullOrEmptyAuthorizationResponseState(); - if (Options.SkipUnrecognizedRequests) + // Fail if state is missing, it's required for the correlation id. + if (string.IsNullOrEmpty(authorizationResponse.State)) { - return AuthenticateResult.Skip(); + // This wasn't a valid OIDC message, it may not have been intended for us. + Logger.NullOrEmptyAuthorizationResponseState(); + if (Options.SkipUnrecognizedRequests) + { + return AuthenticateResult.Skip(); + } + return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty); } - return AuthenticateResult.Fail(Resources.MessageStateIsNullOrEmpty); + + // if state exists and we failed to 'unprotect' this is not a message we should process. + properties = Options.StateDataFormat.Unprotect(authorizationResponse.State); } - // if state exists and we failed to 'unprotect' this is not a message we should process. - var properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(authorizationResponse.State)); if (properties == null) { Logger.UnableToReadAuthorizationResponseState(); @@ -382,17 +392,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect return AuthenticateResult.Fail(Resources.MessageStateIsInvalid); } - // if any of the error fields are set, throw error null - if (!string.IsNullOrEmpty(authorizationResponse.Error)) - { - Logger.AuthorizationResponseError( - authorizationResponse.Error, - authorizationResponse.ErrorDescription ?? "ErrorDecription null", - authorizationResponse.ErrorUri ?? "ErrorUri null"); - - return AuthenticateResult.Fail(new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, authorizationResponse.Error, authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null"))); - } - string userstate = null; properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate); authorizationResponse.State = userstate; @@ -402,20 +401,25 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect return AuthenticateResult.Fail("Correlation failed."); } + // if any of the error fields are set, throw error null + if (!string.IsNullOrEmpty(authorizationResponse.Error)) + { + Logger.AuthorizationResponseError( + authorizationResponse.Error, + authorizationResponse.ErrorDescription ?? "ErrorDecription null", + authorizationResponse.ErrorUri ?? "ErrorUri null"); + + return AuthenticateResult.Fail(new OpenIdConnectProtocolException( + string.Format(CultureInfo.InvariantCulture, Resources.MessageContainsError, authorizationResponse.Error, + authorizationResponse.ErrorDescription ?? "ErrorDecription null", authorizationResponse.ErrorUri ?? "ErrorUri null"))); + } + if (_configuration == null && Options.ConfigurationManager != null) { Logger.UpdatingConfiguration(); _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } - var authorizationResponseReceivedContext = await RunAuthorizationResponseReceivedEventAsync(authorizationResponse, properties); - if (CheckEventResult(authorizationResponseReceivedContext, out result)) - { - return result; - } - authorizationResponse = authorizationResponseReceivedContext.ProtocolMessage; - properties = authorizationResponseReceivedContext.Properties; - PopulateSessionProperties(authorizationResponse, properties); AuthenticationTicket ticket = null; @@ -434,6 +438,17 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { nonce = ReadNonceCookie(nonce); } + + var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, null, properties, ticket, jwt, nonce); + if (tokenValidatedContext.CheckEventResult(out result)) + { + return result; + } + authorizationResponse = tokenValidatedContext.ProtocolMessage; + properties = tokenValidatedContext.Properties; + ticket = tokenValidatedContext.Ticket; + jwt = tokenValidatedContext.SecurityToken; + nonce = tokenValidatedContext.Nonce; } Options.ProtocolValidator.ValidateAuthenticationResponse(new OpenIdConnectProtocolValidationContext() @@ -444,15 +459,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect Nonce = nonce }); - // TODO: AuthorizationResponseValidated event? - OpenIdConnectMessage tokenEndpointResponse = null; // Authorization Code or Hybrid flow if (!string.IsNullOrEmpty(authorizationResponse.Code)) { var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(authorizationResponse, properties, ticket, jwt); - if (CheckEventResult(authorizationCodeReceivedContext, out result)) + if (authorizationCodeReceivedContext.CheckEventResult(out result)) { return result; } @@ -469,13 +482,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect tokenEndpointResponse = await RedeemAuthorizationCodeAsync(tokenEndpointRequest); } - var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties); - if (CheckEventResult(authorizationCodeRedeemedContext, out result)) + var tokenResponseReceivedContext = await RunTokenResponseReceivedEventAsync(authorizationResponse, tokenEndpointResponse, properties); + if (tokenResponseReceivedContext.CheckEventResult(out result)) { return result; } - authorizationResponse = authorizationCodeRedeemedContext.ProtocolMessage; - tokenEndpointResponse = authorizationCodeRedeemedContext.TokenEndpointResponse; + authorizationResponse = tokenResponseReceivedContext.ProtocolMessage; + tokenEndpointResponse = tokenResponseReceivedContext.TokenEndpointResponse; // We only have to process the IdToken if we didn't already get one in the AuthorizationResponse if (ticket == null) @@ -491,6 +504,18 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect { nonce = ReadNonceCookie(nonce); } + + var tokenValidatedContext = await RunTokenValidatedEventAsync(authorizationResponse, tokenEndpointResponse, properties, ticket, jwt, nonce); + if (tokenValidatedContext.CheckEventResult(out result)) + { + return result; + } + authorizationResponse = tokenValidatedContext.ProtocolMessage; + tokenEndpointResponse = tokenValidatedContext.TokenEndpointResponse; + properties = tokenValidatedContext.Properties; + ticket = tokenValidatedContext.Ticket; + jwt = tokenValidatedContext.SecurityToken; + nonce = tokenValidatedContext.Nonce; } // Validate the token response if it wasn't provided manually @@ -506,15 +531,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } } - var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(authorizationResponse, ticket, properties, tokenEndpointResponse); - if (CheckEventResult(authenticationValidatedContext, out result)) - { - return result; - } - authorizationResponse = authenticationValidatedContext.ProtocolMessage; - tokenEndpointResponse = authenticationValidatedContext.TokenEndpointResponse; - ticket = authenticationValidatedContext.Ticket; - if (Options.SaveTokens) { SaveTokens(ticket.Properties, tokenEndpointResponse ?? authorizationResponse); @@ -542,7 +558,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } var authenticationFailedContext = await RunAuthenticationFailedEventAsync(authorizationResponse, exception); - if (CheckEventResult(authenticationFailedContext, out result)) + if (authenticationFailedContext.CheckEventResult(out result)) { return result; } @@ -551,22 +567,6 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } } - private bool CheckEventResult(BaseControlContext context, out AuthenticateResult result) - { - if (context.HandledResponse) - { - result = AuthenticateResult.Success(context.Ticket); - return true; - } - else if (context.Skipped) - { - result = AuthenticateResult.Skip(); - return true; - } - result = null; - return false; - } - private void PopulateSessionProperties(OpenIdConnectMessage message, AuthenticationProperties properties) { if (!string.IsNullOrEmpty(message.SessionState)) @@ -643,7 +643,7 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user); AuthenticateResult result; - if (CheckEventResult(userInformationReceivedContext, out result)) + if (userInformationReceivedContext.CheckEventResult(out result)) { return result; } @@ -830,12 +830,13 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect } } - private async Task RunMessageReceivedEventAsync(OpenIdConnectMessage message) + private async Task RunMessageReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties) { Logger.MessageReceived(message.BuildRedirectUrl()); var messageReceivedContext = new MessageReceivedContext(Context, Options) { - ProtocolMessage = message + ProtocolMessage = message, + Properties = properties, }; await Options.Events.MessageReceived(messageReceivedContext); @@ -851,23 +852,29 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect return messageReceivedContext; } - private async Task RunAuthorizationResponseReceivedEventAsync(OpenIdConnectMessage message, AuthenticationProperties properties) + private async Task RunTokenValidatedEventAsync(OpenIdConnectMessage authorizationResponse, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt, string nonce) { - Logger.AuthorizationResponseReceived(); - var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options, properties) + var tokenValidatedContext = new TokenValidatedContext(Context, Options) { - ProtocolMessage = message + ProtocolMessage = authorizationResponse, + TokenEndpointResponse = tokenEndpointResponse, + Properties = properties, + Ticket = ticket, + SecurityToken = jwt, + Nonce = nonce, }; - await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext); - if (authorizationResponseReceivedContext.HandledResponse) + + await Options.Events.TokenValidated(tokenValidatedContext); + if (tokenValidatedContext.HandledResponse) { - Logger.AuthorizationResponseReceivedHandledResponse(); + Logger.TokenValidatedHandledResponse(); } - else if (authorizationResponseReceivedContext.Skipped) + else if (tokenValidatedContext.Skipped) { - Logger.AuthorizationResponseReceivedSkipped(); + Logger.TokenValidatedSkipped(); } - return authorizationResponseReceivedContext; + + return tokenValidatedContext; } private async Task RunAuthorizationCodeReceivedEventAsync(OpenIdConnectMessage authorizationResponse, AuthenticationProperties properties, AuthenticationTicket ticket, JwtSecurityToken jwt) @@ -909,46 +916,23 @@ namespace Microsoft.AspNetCore.Authentication.OpenIdConnect private async Task RunTokenResponseReceivedEventAsync(OpenIdConnectMessage message, OpenIdConnectMessage tokenEndpointResponse, AuthenticationProperties properties) { Logger.TokenResponseReceived(); - - var tokenResponseReceivedContext = new TokenResponseReceivedContext(Context, Options, properties) + var eventContext = new TokenResponseReceivedContext(Context, Options, properties) { ProtocolMessage = message, TokenEndpointResponse = tokenEndpointResponse }; - await Options.Events.TokenResponseReceived(tokenResponseReceivedContext); - if (tokenResponseReceivedContext.HandledResponse) + await Options.Events.TokenResponseReceived(eventContext); + if (eventContext.HandledResponse) { - Logger.AuthorizationCodeRedeemedContextHandledResponse(); + Logger.TokenResponseReceivedHandledResponse(); } - else if (tokenResponseReceivedContext.Skipped) + else if (eventContext.Skipped) { - Logger.AuthorizationCodeRedeemedContextSkipped(); + Logger.TokenResponseReceivedSkipped(); } - return tokenResponseReceivedContext; - } - - private async Task RunAuthenticationValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket, AuthenticationProperties properties, OpenIdConnectMessage tokenResponse) - { - var authenticationValidatedContext = new AuthenticationValidatedContext(Context, Options, properties) - { - Ticket = ticket, - ProtocolMessage = message, - TokenEndpointResponse = tokenResponse, - }; - - await Options.Events.AuthenticationValidated(authenticationValidatedContext); - if (authenticationValidatedContext.HandledResponse) - { - Logger.AuthenticationValidatedHandledResponse(); - } - else if (authenticationValidatedContext.Skipped) - { - Logger.AuthenticationValidatedSkipped(); - } - - return authenticationValidatedContext; + return eventContext; } private async Task RunUserInformationReceivedEventAsync(AuthenticationTicket ticket, OpenIdConnectMessage message, JObject user) diff --git a/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs b/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs index db81ad704c..24f3ba8e53 100644 --- a/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs +++ b/src/Microsoft.AspNetCore.Authentication/Events/BaseControlContext.cs @@ -46,5 +46,21 @@ namespace Microsoft.AspNetCore.Authentication /// Gets or set the to return if this event signals it handled the event. /// public AuthenticationTicket Ticket { get; set; } + + public bool CheckEventResult(out AuthenticateResult result) + { + if (HandledResponse) + { + result = AuthenticateResult.Success(Ticket); + return true; + } + else if (Skipped) + { + result = AuthenticateResult.Skip(); + return true; + } + result = null; + return false; + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs index 72d13a4c9e..81c03dc83a 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer AutomaticAuthenticate = true, Events = new JwtBearerEvents() { - OnReceivingToken = context => + OnMessageReceived = context => { var claims = new[] { @@ -143,39 +143,6 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal("", response.ResponseText); } - [Fact] - public async Task CustomTokenReceived() - { - var server = CreateServer(new JwtBearerOptions - { - AutomaticAuthenticate = true, - Events = new JwtBearerEvents() - { - OnReceivedToken = context => - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") - }; - - context.Ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, context.Options.AuthenticationScheme)), - new AuthenticationProperties(), context.Options.AuthenticationScheme); - - context.HandleResponse(); - - return Task.FromResult(null); - } - } - }); - - var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); - Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); - Assert.Equal("Bob le Magnifique", response.ResponseText); - } - [Fact] public async Task CustomTokenValidated() { @@ -184,7 +151,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer AutomaticAuthenticate = true, Events = new JwtBearerEvents() { - OnValidatedToken = context => + OnTokenValidated = context => { // Retrieve the NameIdentifier claim from the identity // returned by the custom security token validator. @@ -204,6 +171,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } } }; + options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator(options.AuthenticationScheme)); var server = CreateServer(options); @@ -215,67 +183,37 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer [Fact] public async Task RetrievingTokenFromAlternateLocation() { - var server = CreateServer(new JwtBearerOptions + var options = new JwtBearerOptions() { AutomaticAuthenticate = true, Events = new JwtBearerEvents() { - OnReceivingToken = context => + OnMessageReceived = context => { context.Token = "CustomToken"; - return Task.FromResult(null); - }, - OnReceivedToken = context => - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") - }; - - context.Ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, context.Options.AuthenticationScheme)), - new AuthenticationProperties(), context.Options.AuthenticationScheme); - - context.HandleResponse(); - return Task.FromResult(null); } } - }); + }; + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT", token => + { + Assert.Equal("CustomToken", token); + })); + var server = CreateServer(options); var response = await SendAsync(server, "http://example.com/oauth", "Bearer Token"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); - Assert.Equal("Bob le Magnifique", response.ResponseText); + Assert.Equal("Bob le Tout Puissant", response.ResponseText); } [Fact] public async Task BearerTurns401To403IfAuthenticated() { - var server = CreateServer(new JwtBearerOptions - { - Events = new JwtBearerEvents() - { - OnReceivedToken = context => - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") - }; - - context.Ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, context.Options.AuthenticationScheme)), - new AuthenticationProperties(), context.Options.AuthenticationScheme); - - context.HandleResponse(); - - return Task.FromResult(null); - } - } - }); + var options = new JwtBearerOptions(); + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); + var server = CreateServer(options); var response = await SendAsync(server, "http://example.com/unauthorized", "Bearer Token"); Assert.Equal(HttpStatusCode.Forbidden, response.Response.StatusCode); @@ -284,52 +222,26 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer [Fact] public async Task BearerDoesNothingTo401IfNotAuthenticated() { - var server = CreateServer(new JwtBearerOptions - { - Events = new JwtBearerEvents() - { - OnReceivedToken = context => - { - var claims = new[] - { - new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), - new Claim(ClaimTypes.Email, "bob@contoso.com"), - new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") - }; - - context.Ticket = new AuthenticationTicket( - new ClaimsPrincipal(new ClaimsIdentity(claims, context.Options.AuthenticationScheme)), - new AuthenticationProperties(), context.Options.AuthenticationScheme); - - context.HandleResponse(); - - return Task.FromResult(null); - } - } - }); + var server = CreateServer(new JwtBearerOptions()); var response = await SendAsync(server, "http://example.com/unauthorized"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); } [Fact] - public async Task EventOnReceivingTokenSkipped_NoMoreEventsExecuted() + public async Task EventOnMessageReceivedSkipped_NoMoreEventsExecuted() { var server = CreateServer(new JwtBearerOptions { AutomaticAuthenticate = true, Events = new JwtBearerEvents() { - OnReceivingToken = context => + OnMessageReceived = context => { context.SkipToNextMiddleware(); return Task.FromResult(0); }, - OnReceivedToken = context => - { - throw new NotImplementedException(); - }, - OnValidatedToken = context => + OnTokenValidated = context => { throw new NotImplementedException(); }, @@ -350,47 +262,14 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } [Fact] - public async Task EventOnReceivedTokenSkipped_NoMoreEventsExecuted() - { - var server = CreateServer(new JwtBearerOptions - { - AutomaticAuthenticate = true, - Events = new JwtBearerEvents() - { - OnReceivedToken = context => - { - context.SkipToNextMiddleware(); - return Task.FromResult(0); - }, - OnValidatedToken = context => - { - throw new NotImplementedException(); - }, - OnAuthenticationFailed = context => - { - throw new NotImplementedException(context.Exception.ToString()); - }, - OnChallenge = context => - { - throw new NotImplementedException(); - }, - } - }); - - var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); - Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); - Assert.Equal(string.Empty, response.ResponseText); - } - - [Fact] - public async Task EventOnValidatedTokenSkipped_NoMoreEventsExecuted() + public async Task EventOnTokenValidatedSkipped_NoMoreEventsExecuted() { var options = new JwtBearerOptions { AutomaticAuthenticate = true, Events = new JwtBearerEvents() { - OnValidatedToken = context => + OnTokenValidated = context => { context.SkipToNextMiddleware(); return Task.FromResult(0); @@ -422,7 +301,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer AutomaticAuthenticate = true, Events = new JwtBearerEvents() { - OnValidatedToken = context => + OnTokenValidated = context => { throw new Exception("Test Exception"); }, @@ -493,9 +372,17 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer class BlobTokenValidator : ISecurityTokenValidator { + private Action _tokenValidator; + public BlobTokenValidator(string authenticationScheme) { AuthenticationScheme = authenticationScheme; + + } + public BlobTokenValidator(string authenticationScheme, Action tokenValidator) + { + AuthenticationScheme = authenticationScheme; + _tokenValidator = tokenValidator; } public string AuthenticationScheme { get; } @@ -519,6 +406,10 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) { validatedToken = null; + if (_tokenValidator != null) + { + _tokenValidator(securityToken); + } var claims = new[] { diff --git a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs index cadcf80884..ebc59b9ee8 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs @@ -150,12 +150,12 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect options.AutomaticChallenge = true; options.Events = new OpenIdConnectEvents() { - OnRedirectToAuthenticationEndpoint = (context) => + OnRedirectToIdentityProvider = (context) => { context.ProtocolMessage = fakeOpenIdRequestMessage; return Task.FromResult(0); }, - OnRedirectToEndSessionEndpoint = (context) => + OnRedirectToIdentityProviderForSignOut = (context) => { context.ProtocolMessage = fakeOpenIdRequestMessage; return Task.FromResult(0); @@ -208,7 +208,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect options.AutomaticChallenge = challenge.Equals(ChallengeWithOutContext); options.Events = new OpenIdConnectEvents() { - OnRedirectToAuthenticationEndpoint = context => + OnRedirectToIdentityProvider = context => { context.ProtocolMessage.State = userState; context.ProtocolMessage.RedirectUri = queryValues.RedirectUri; @@ -258,7 +258,7 @@ namespace Microsoft.AspNetCore.Authentication.Tests.OpenIdConnect var options = GetOptions(DefaultParameters(), queryValues); options.Events = new OpenIdConnectEvents() { - OnRedirectToAuthenticationEndpoint = context => + OnRedirectToIdentityProvider = context => { context.ProtocolMessage.ClientId = queryValuesSetInEvent.ClientId; context.ProtocolMessage.RedirectUri = queryValuesSetInEvent.RedirectUri;