diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthenticationCompletedContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthenticationCompletedContext.cs new file mode 100644 index 000000000..820b4f948 --- /dev/null +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthenticationCompletedContext.cs @@ -0,0 +1,15 @@ +// 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; + +namespace Microsoft.AspNet.Authentication.OpenIdConnect +{ + public class AuthenticationCompletedContext : BaseControlContext + { + public AuthenticationCompletedContext(HttpContext context, OpenIdConnectOptions options) + : base(context, options) + { + } + } +} diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/SecurityTokenReceivedContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthenticationValidatedContext.cs similarity index 62% rename from src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/SecurityTokenReceivedContext.cs rename to src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthenticationValidatedContext.cs index 570942c6a..15d5f7304 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/SecurityTokenReceivedContext.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthenticationValidatedContext.cs @@ -6,15 +6,15 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect { - public class SecurityTokenReceivedContext : BaseControlContext + public class AuthenticationValidatedContext : BaseControlContext { - public SecurityTokenReceivedContext(HttpContext context, OpenIdConnectOptions options) + public AuthenticationValidatedContext(HttpContext context, OpenIdConnectOptions options) : base(context, options) { } - public string SecurityToken { get; set; } - public OpenIdConnectMessage ProtocolMessage { get; set; } + + public OpenIdConnectTokenEndpointResponse TokenEndpointResponse { get; set; } } } diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/SecurityTokenValidatedContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs similarity index 59% rename from src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/SecurityTokenValidatedContext.cs rename to src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs index 70392282f..cd2a3c65b 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/SecurityTokenValidatedContext.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthorizationResponseReceivedContext.cs @@ -2,17 +2,20 @@ // 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 SecurityTokenValidatedContext : BaseControlContext + public class AuthorizationResponseReceivedContext : BaseControlContext { - public SecurityTokenValidatedContext(HttpContext context, OpenIdConnectOptions options) + public AuthorizationResponseReceivedContext(HttpContext context, OpenIdConnectOptions options) : base(context, options) { } public OpenIdConnectMessage ProtocolMessage { get; set; } + + public AuthenticationProperties Properties { get; set; } } } diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs index a6b4fcedf..a5953743b 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/IOpenIdConnectEvents.cs @@ -10,20 +10,30 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect /// public interface IOpenIdConnectEvents { + /// + /// Invoked when the authentication process completes. + /// + Task AuthenticationCompleted(AuthenticationCompletedContext context); + /// /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. /// 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 after "authorization code" is redeemed for tokens at the token endpoint. + /// Invoked when an authorization response is received. /// - Task AuthorizationCodeRedeemed(AuthorizationCodeRedeemedContext context); + Task AuthorizationResponseReceived(AuthorizationResponseReceivedContext context); /// /// Invoked when a protocol message is first received. @@ -31,18 +41,23 @@ public interface IOpenIdConnectEvents Task MessageReceived(MessageReceivedContext context); /// - /// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge. + /// Invoked before redirecting to the identity provider to authenticate. /// - Task RedirectToIdentityProvider(RedirectToIdentityProviderContext context); + Task RedirectToAuthenticationEndpoint(RedirectContext context); /// - /// Invoked with the security token that has been extracted from the protocol message. + /// Invoked before redirecting to the identity provider to sign out. + /// + Task RedirectToEndSessionEndpoint(RedirectContext context); + + /// + /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. /// - Task SecurityTokenReceived(SecurityTokenReceivedContext context); + Task TokenResponseReceived(TokenResponseReceivedContext context); /// - /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. + /// Invoked when user information is retrieved from the UserInfoEndpoint. /// - Task SecurityTokenValidated(SecurityTokenValidatedContext context); + Task UserInformationReceived(UserInformationReceivedContext context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs index 90ab05eed..a2d65c12b 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/OpenIdConnectEvents.cs @@ -11,20 +11,30 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect /// public class OpenIdConnectEvents : IOpenIdConnectEvents { + /// + /// Invoked when the authentication process completes. + /// + public Func OnAuthenticationCompleted { get; set; } = context => Task.FromResult(0); + /// /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. /// 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 after "authorization code" is redeemed for tokens at the token endpoint. + /// Invoked when an authorization response is received. /// - public Func OnAuthorizationCodeRedeemed { get; set; } = context => Task.FromResult(0); + public Func OnAuthorizationResponseReceived { get; set; } = context => Task.FromResult(0); /// /// Invoked when a protocol message is first received. @@ -32,32 +42,43 @@ public class OpenIdConnectEvents : IOpenIdConnectEvents public Func OnMessageReceived { get; set; } = context => Task.FromResult(0); /// - /// Invoked to manipulate redirects to the identity provider for SignIn, SignOut, or Challenge. + /// Invoked before redirecting to the identity provider to authenticate. /// - public Func OnRedirectToIdentityProvider { get; set; } = context => Task.FromResult(0); + public Func OnRedirectToAuthenticationEndpoint { get; set; } = context => Task.FromResult(0); /// - /// Invoked with the security token that has been extracted from the protocol message. + /// Invoked before redirecting to the identity provider to sign out. /// - public Func OnSecurityTokenReceived { get; set; } = context => Task.FromResult(0); + public Func OnRedirectToEndSessionEndpoint { get; set; } = context => Task.FromResult(0); /// - /// Invoked after the security token has passed validation and a ClaimsIdentity has been generated. + /// Invoked after "authorization code" is redeemed for tokens at the token endpoint. + /// + public Func OnTokenResponseReceived { get; set; } = context => Task.FromResult(0); + + /// + /// Invoked when user information is retrieved from the UserInfoEndpoint. /// - public Func OnSecurityTokenValidated { get; set; } = context => Task.FromResult(0); + public Func OnUserInformationReceived { get; set; } = context => Task.FromResult(0); + + public virtual Task AuthenticationCompleted(AuthenticationCompletedContext context) => OnAuthenticationCompleted(context); 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 AuthorizationCodeRedeemed(AuthorizationCodeRedeemedContext context) => OnAuthorizationCodeRedeemed(context); + public virtual Task AuthorizationResponseReceived(AuthorizationResponseReceivedContext context) => OnAuthorizationResponseReceived(context); public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context); - public virtual Task RedirectToIdentityProvider(RedirectToIdentityProviderContext context) => OnRedirectToIdentityProvider(context); + public virtual Task RedirectToAuthenticationEndpoint(RedirectContext context) => OnRedirectToAuthenticationEndpoint(context); + + public virtual Task RedirectToEndSessionEndpoint(RedirectContext context) => OnRedirectToEndSessionEndpoint(context); - public virtual Task SecurityTokenReceived(SecurityTokenReceivedContext context) => OnSecurityTokenReceived(context); + public virtual Task TokenResponseReceived(TokenResponseReceivedContext context) => OnTokenResponseReceived(context); - public virtual Task SecurityTokenValidated(SecurityTokenValidatedContext context) => OnSecurityTokenValidated(context); + public virtual Task UserInformationReceived(UserInformationReceivedContext context) => OnUserInformationReceived(context); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/RedirectContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/RedirectContext.cs new file mode 100644 index 000000000..14b168aaa --- /dev/null +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/RedirectContext.cs @@ -0,0 +1,25 @@ +// 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.IdentityModel.Protocols.OpenIdConnect; + +namespace Microsoft.AspNet.Authentication.OpenIdConnect +{ + /// + /// When a user configures the to be notified prior to redirecting to an IdentityProvider + /// an instance of is passed to the 'RedirectToAuthenticationEndpoint' or 'RedirectToEndSessionEndpoint' events. + /// + public class RedirectContext : BaseControlContext + { + public RedirectContext(HttpContext context, OpenIdConnectOptions options) + : base(context, options) + { + } + + /// + /// Gets or sets the . + /// + public OpenIdConnectMessage ProtocolMessage { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/RedirectToIdentityProviderContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/RedirectToIdentityProviderContext.cs deleted file mode 100644 index ca7ba6ca8..000000000 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/RedirectToIdentityProviderContext.cs +++ /dev/null @@ -1,30 +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 Microsoft.AspNet.Http; -using Microsoft.Framework.Internal; -using Microsoft.IdentityModel.Protocols.OpenIdConnect; - -namespace Microsoft.AspNet.Authentication.OpenIdConnect -{ - /// - /// When a user configures the to be notified prior to redirecting to an IdentityProvider - /// an instance of is passed to the 'RedirectToIdentityProvider" event. - /// - /// protocol specific message. - /// protocol specific options. - public class RedirectToIdentityProviderContext : BaseControlContext - { - public RedirectToIdentityProviderContext([NotNull] HttpContext context, [NotNull] OpenIdConnectOptions options) - : base(context, options) - { - } - - /// - /// Gets or sets the . - /// - /// if 'value' is null. - public OpenIdConnectMessage ProtocolMessage { get; [param: NotNull] set; } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthorizationCodeRedeemedContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs similarity index 70% rename from src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthorizationCodeRedeemedContext.cs rename to src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs index 479431912..e4f1bd7a0 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/AuthorizationCodeRedeemedContext.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/TokenResponseReceivedContext.cs @@ -6,21 +6,16 @@ namespace Microsoft.AspNet.Authentication.OpenIdConnect /// /// This Context can be used to be informed when an 'AuthorizationCode' is redeemed for tokens at the token endpoint. /// - public class AuthorizationCodeRedeemedContext : BaseControlContext + public class TokenResponseReceivedContext : BaseControlContext { /// - /// Creates a + /// Creates a /// - public AuthorizationCodeRedeemedContext(HttpContext context, OpenIdConnectOptions options) + public TokenResponseReceivedContext(HttpContext context, OpenIdConnectOptions options) : base(context, options) { } - /// - /// Gets or sets the 'code'. - /// - public string Code { get; set; } - /// /// Gets or sets the that contains the tokens and json response received after redeeming the code at the token endpoint. /// diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs new file mode 100644 index 000000000..27c989bb6 --- /dev/null +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Events/UserInformationReceivedContext.cs @@ -0,0 +1,21 @@ +// 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.IdentityModel.Protocols.OpenIdConnect; +using Newtonsoft.Json.Linq; + +namespace Microsoft.AspNet.Authentication.OpenIdConnect +{ + public class UserInformationReceivedContext : BaseControlContext + { + public UserInformationReceivedContext(HttpContext context, OpenIdConnectOptions options) + : base(context, options) + { + } + + public OpenIdConnectMessage ProtocolMessage { get; set; } + + public JObject User { get; set; } + } +} diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs index 182f8d1ae..9f1ea0651 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectHandler.cs @@ -74,7 +74,6 @@ protected override async Task HandleSignOutAsync(SignOutContext signout) var message = new OpenIdConnectMessage() { IssuerAddress = _configuration == null ? string.Empty : (_configuration.EndSessionEndpoint ?? string.Empty), - RequestType = OpenIdConnectRequestType.LogoutRequest, }; // Set End_Session_Endpoint in order: @@ -90,24 +89,24 @@ protected override async Task HandleSignOutAsync(SignOutContext signout) message.PostLogoutRedirectUri = Options.PostLogoutRedirectUri; } - var redirectToIdentityProviderContext = new RedirectToIdentityProviderContext(Context, Options) + var redirectContext = new RedirectContext(Context, Options) { ProtocolMessage = message }; - await Options.Events.RedirectToIdentityProvider(redirectToIdentityProviderContext); - if (redirectToIdentityProviderContext.HandledResponse) + await Options.Events.RedirectToEndSessionEndpoint(redirectContext); + if (redirectContext.HandledResponse) { - Logger.LogVerbose(Resources.OIDCH_0034_RedirectToIdentityProviderContextHandledResponse); + Logger.LogVerbose("RedirectToEndSessionEndpoint.HandledResponse"); return; } - else if (redirectToIdentityProviderContext.Skipped) + else if (redirectContext.Skipped) { - Logger.LogVerbose(Resources.OIDCH_0035_RedirectToIdentityProviderContextSkipped); + Logger.LogVerbose("RedirectToEndSessionEndpoint.Skipped"); return; } - message = redirectToIdentityProviderContext.ProtocolMessage; + message = redirectContext.ProtocolMessage; if (Options.AuthenticationMethod == OpenIdConnectRedirectBehavior.RedirectGet) { @@ -183,8 +182,6 @@ protected override async Task HandleUnauthorizedAsync([NotNull] ChallengeC ClientId = Options.ClientId, IssuerAddress = _configuration?.AuthorizationEndpoint ?? string.Empty, RedirectUri = Options.RedirectUri, - // [brentschmaltz] - #215 this should be a property on RedirectToIdentityProviderContext not on the OIDCMessage. - RequestType = OpenIdConnectRequestType.AuthenticationRequest, Resource = Options.Resource, ResponseType = Options.ResponseType, Scope = string.Join(" ", Options.Scope) @@ -207,30 +204,30 @@ protected override async Task HandleUnauthorizedAsync([NotNull] ChallengeC GenerateCorrelationId(properties); - var redirectToIdentityProviderContext = new RedirectToIdentityProviderContext(Context, Options) + var redirectContext = new RedirectContext(Context, Options) { ProtocolMessage = message }; - await Options.Events.RedirectToIdentityProvider(redirectToIdentityProviderContext); - if (redirectToIdentityProviderContext.HandledResponse) + await Options.Events.RedirectToAuthenticationEndpoint(redirectContext); + if (redirectContext.HandledResponse) { - Logger.LogVerbose(Resources.OIDCH_0034_RedirectToIdentityProviderContextHandledResponse); + Logger.LogVerbose("RedirectToAuthenticationEndpoint.HandledResponse"); return true; } - else if (redirectToIdentityProviderContext.Skipped) + else if (redirectContext.Skipped) { - Logger.LogVerbose(Resources.OIDCH_0035_RedirectToIdentityProviderContextSkipped); + Logger.LogVerbose("RedirectToAuthenticationEndpoint.Skipped"); return false; } - if (!string.IsNullOrEmpty(redirectToIdentityProviderContext.ProtocolMessage.State)) + message = redirectContext.ProtocolMessage; + + if (!string.IsNullOrEmpty(message.State)) { - properties.Items[OpenIdConnectDefaults.UserstatePropertiesKey] = redirectToIdentityProviderContext.ProtocolMessage.State; + properties.Items[OpenIdConnectDefaults.UserstatePropertiesKey] = message.State; } - message = redirectToIdentityProviderContext.ProtocolMessage; - var redirectUriForCode = message.RedirectUri; if (string.IsNullOrEmpty(redirectUriForCode)) { @@ -350,36 +347,35 @@ protected override async Task HandleAuthenticateAsync() { return null; } + message = messageReceivedContext.ProtocolMessage; - var properties = new AuthenticationProperties(); - - // if state is missing, just log it + // Fail if state is missing, it's required for the correlation id. if (string.IsNullOrEmpty(message.State)) { - Logger.LogWarning(Resources.OIDCH_0004_MessageStateIsNullOrEmpty); + Logger.LogError(Resources.OIDCH_0004_MessageStateIsNullOrEmpty); + return null; } - else - { - // if state exists and we failed to 'unprotect' this is not a message we should process. - properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(message.State)); - if (properties == null) - { - Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid); - return null; - } - string userstate = null; - properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate); - message.State = userstate; + // if state exists and we failed to 'unprotect' this is not a message we should process. + var properties = Options.StateDataFormat.Unprotect(Uri.UnescapeDataString(message.State)); + if (properties == null) + { + Logger.LogError(Resources.OIDCH_0005_MessageStateIsInvalid); + return null; } // if any of the error fields are set, throw error null if (!string.IsNullOrEmpty(message.Error)) { Logger.LogError(Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null"); - throw new SecurityTokenException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null")); + throw new OpenIdConnectProtocolException(string.Format(CultureInfo.InvariantCulture, Resources.OIDCH_0006_MessageContainsError, message.Error, message.ErrorDescription ?? "ErrorDecription null", message.ErrorUri ?? "ErrorUri null")); } + string userstate = null; + properties.Items.TryGetValue(OpenIdConnectDefaults.UserstatePropertiesKey, out userstate); + message.State = userstate; + + if (!ValidateCorrelationId(properties)) { return null; @@ -391,6 +387,26 @@ protected override async Task HandleAuthenticateAsync() _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } + Logger.LogDebug("Authorization response received."); + var authorizationResponseReceivedContext = new AuthorizationResponseReceivedContext(Context, Options) + { + ProtocolMessage = message, + Properties = properties + }; + await Options.Events.AuthorizationResponseReceived(authorizationResponseReceivedContext); + if (authorizationResponseReceivedContext.HandledResponse) + { + Logger.LogVerbose("AuthorizationResponseReceived.HandledResponse"); + return authorizationResponseReceivedContext.AuthenticationTicket; + } + else if (authorizationResponseReceivedContext.Skipped) + { + Logger.LogVerbose("AuthorizationResponseReceived.Skipped"); + return null; + } + message = authorizationResponseReceivedContext.ProtocolMessage; + properties = authorizationResponseReceivedContext.Properties; + if (string.IsNullOrEmpty(message.IdToken) && !string.IsNullOrEmpty(message.Code)) { return await HandleCodeOnlyFlow(message, properties); @@ -433,13 +449,12 @@ protected override async Task HandleAuthenticateAsync() } } + // Authorization Code Flow private async Task HandleCodeOnlyFlow(OpenIdConnectMessage message, AuthenticationProperties properties) { AuthenticationTicket ticket = null; JwtSecurityToken jwt = null; - OpenIdConnectTokenEndpointResponse tokenEndpointResponse = null; - string idToken = null; var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt); if (authorizationCodeReceivedContext.HandledResponse) { @@ -449,14 +464,15 @@ private async Task HandleCodeOnlyFlow(OpenIdConnectMessage { return null; } + message = authorizationCodeReceivedContext.ProtocolMessage; + var code = authorizationCodeReceivedContext.Code; // Redeeming authorization code for tokens - Logger.LogDebug(Resources.OIDCH_0038_Redeeming_Auth_Code, message.Code); + Logger.LogDebug(Resources.OIDCH_0038_Redeeming_Auth_Code, code); - tokenEndpointResponse = await RedeemAuthorizationCodeAsync(message.Code, authorizationCodeReceivedContext.RedirectUri); - idToken = tokenEndpointResponse.Message.IdToken; + var tokenEndpointResponse = await RedeemAuthorizationCodeAsync(code, authorizationCodeReceivedContext.RedirectUri); - var authorizationCodeRedeemedContext = await RunAuthorizationCodeRedeemedEventAsync(message, tokenEndpointResponse); + var authorizationCodeRedeemedContext = await RunTokenResponseReceivedEventAsync(message, tokenEndpointResponse); if (authorizationCodeRedeemedContext.HandledResponse) { return authorizationCodeRedeemedContext.AuthenticationTicket; @@ -466,63 +482,61 @@ private async Task HandleCodeOnlyFlow(OpenIdConnectMessage return null; } + message = authorizationCodeRedeemedContext.ProtocolMessage; + tokenEndpointResponse = authorizationCodeRedeemedContext.TokenEndpointResponse; + // no need to validate signature when token is received using "code flow" as per spec [http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation]. var validationParameters = Options.TokenValidationParameters.Clone(); validationParameters.ValidateSignature = false; - ticket = ValidateToken(idToken, message, properties, validationParameters, out jwt); + ticket = ValidateToken(tokenEndpointResponse.ProtocolMessage.IdToken, message, properties, validationParameters, out jwt); await ValidateOpenIdConnectProtocolAsync(null, message); - if (Options.GetClaimsFromUserInfoEndpoint) + var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse); + if (authenticationValidatedContext.HandledResponse) { - Logger.LogDebug(Resources.OIDCH_0040_Sending_Request_UIEndpoint); - ticket = await GetUserInformationAsync(properties, tokenEndpointResponse.Message, ticket); + return authenticationValidatedContext.AuthenticationTicket; } - - var securityTokenValidatedContext = await RunSecurityTokenValidatedEventAsync(message, ticket); - if (securityTokenValidatedContext.HandledResponse) + else if (authenticationValidatedContext.Skipped) { - return securityTokenValidatedContext.AuthenticationTicket; + return null; } - else if (securityTokenValidatedContext.Skipped) + ticket = authenticationValidatedContext.AuthenticationTicket; + + if (Options.GetClaimsFromUserInfoEndpoint) { - return null; + Logger.LogDebug(Resources.OIDCH_0040_Sending_Request_UIEndpoint); + ticket = await GetUserInformationAsync(tokenEndpointResponse.ProtocolMessage, ticket); } return ticket; } + // Implicit Flow or Hybrid Flow private async Task HandleIdTokenFlows(OpenIdConnectMessage message, AuthenticationProperties properties) { - AuthenticationTicket ticket = null; - JwtSecurityToken jwt = null; - - var securityTokenReceivedContext = await RunSecurityTokenReceivedEventAsync(message); - if (securityTokenReceivedContext.HandledResponse) - { - return securityTokenReceivedContext.AuthenticationTicket; - } - else if (securityTokenReceivedContext.Skipped) - { - return null; - } + Logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken); + JwtSecurityToken jwt = null; var validationParameters = Options.TokenValidationParameters.Clone(); - ticket = ValidateToken(message.IdToken, message, properties, validationParameters, out jwt); + var ticket = ValidateToken(message.IdToken, message, properties, validationParameters, out jwt); await ValidateOpenIdConnectProtocolAsync(jwt, message); - var securityTokenValidatedContext = await RunSecurityTokenValidatedEventAsync(message, ticket); - if (securityTokenValidatedContext.HandledResponse) + var authenticationValidatedContext = await RunAuthenticationValidatedEventAsync(message, ticket, tokenEndpointResponse: null); + if (authenticationValidatedContext.HandledResponse) { - return securityTokenValidatedContext.AuthenticationTicket; + return authenticationValidatedContext.AuthenticationTicket; } - else if (securityTokenValidatedContext.Skipped) + else if (authenticationValidatedContext.Skipped) { return null; } + message = authenticationValidatedContext.ProtocolMessage; + ticket = authenticationValidatedContext.AuthenticationTicket; + // Hybrid Flow if (message.Code != null) { var authorizationCodeReceivedContext = await RunAuthorizationCodeReceivedEventAsync(message, properties, ticket, jwt); @@ -534,6 +548,7 @@ private async Task HandleIdTokenFlows(OpenIdConnectMessage { return null; } + ticket = authorizationCodeReceivedContext.AuthenticationTicket; } return ticket; @@ -568,17 +583,12 @@ protected virtual async Task RedeemAuthoriza /// /// Goes to UserInfo endpoint to retrieve additional claims and add any unique claims to the given identity. /// - /// Authentication Properties /// message that is being processed /// authentication ticket with claims principal and identities /// Authentication ticket with identity with additional claims, if any. - protected virtual async Task GetUserInformationAsync(AuthenticationProperties properties, OpenIdConnectMessage message, AuthenticationTicket ticket) + protected virtual async Task GetUserInformationAsync(OpenIdConnectMessage message, AuthenticationTicket ticket) { - string userInfoEndpoint = null; - if (_configuration != null) - { - userInfoEndpoint = _configuration.UserInfoEndpoint; - } + string userInfoEndpoint = _configuration?.UserInfoEndpoint; if (string.IsNullOrEmpty(userInfoEndpoint)) { @@ -593,6 +603,18 @@ protected virtual async Task GetUserInformationAsync(Authe var userInfoResponse = await responseMessage.Content.ReadAsStringAsync(); var user = JObject.Parse(userInfoResponse); + var userInformationReceivedContext = await RunUserInformationReceivedEventAsync(ticket, message, user); + if (userInformationReceivedContext.HandledResponse) + { + return userInformationReceivedContext.AuthenticationTicket; + } + else if (userInformationReceivedContext.Skipped) + { + return null; + } + ticket = userInformationReceivedContext.AuthenticationTicket; + user = userInformationReceivedContext.User; + var identity = (ClaimsIdentity)ticket.Principal.Identity; var subjectClaimType = identity.FindFirst(ClaimTypes.NameIdentifier); if (subjectClaimType == null) @@ -846,68 +868,71 @@ private async Task RunAuthorizationCodeReceive return authorizationCodeReceivedContext; } - private async Task RunAuthorizationCodeRedeemedEventAsync(OpenIdConnectMessage message, OpenIdConnectTokenEndpointResponse tokenEndpointResponse) + private async Task RunTokenResponseReceivedEventAsync(OpenIdConnectMessage message, OpenIdConnectTokenEndpointResponse tokenEndpointResponse) { - Logger.LogDebug(Resources.OIDCH_0042_AuthorizationCodeRedeemed, message.Code); - var authorizationCodeRedeemedContext = new AuthorizationCodeRedeemedContext(Context, Options) + Logger.LogDebug("Token response received."); + var tokenResponseReceivedContext = new TokenResponseReceivedContext(Context, Options) { - Code = message.Code, ProtocolMessage = message, TokenEndpointResponse = tokenEndpointResponse }; - await Options.Events.AuthorizationCodeRedeemed(authorizationCodeRedeemedContext); - if (authorizationCodeRedeemedContext.HandledResponse) + await Options.Events.TokenResponseReceived(tokenResponseReceivedContext); + if (tokenResponseReceivedContext.HandledResponse) { Logger.LogVerbose(Resources.OIDCH_0043_AuthorizationCodeRedeemedContextHandledResponse); } - else if (authorizationCodeRedeemedContext.Skipped) + else if (tokenResponseReceivedContext.Skipped) { Logger.LogVerbose(Resources.OIDCH_0044_AuthorizationCodeRedeemedContextSkipped); } - return authorizationCodeRedeemedContext; + return tokenResponseReceivedContext; } - private async Task RunSecurityTokenReceivedEventAsync(OpenIdConnectMessage message) + private async Task RunAuthenticationValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket, OpenIdConnectTokenEndpointResponse tokenEndpointResponse) { - Logger.LogDebug(Resources.OIDCH_0020_IdTokenReceived, message.IdToken); - var securityTokenReceivedContext = new SecurityTokenReceivedContext(Context, Options) + var authenticationValidatedContext = new AuthenticationValidatedContext(Context, Options) { + AuthenticationTicket = ticket, ProtocolMessage = message, + TokenEndpointResponse = tokenEndpointResponse, }; - await Options.Events.SecurityTokenReceived(securityTokenReceivedContext); - if (securityTokenReceivedContext.HandledResponse) + await Options.Events.AuthenticationValidated(authenticationValidatedContext); + if (authenticationValidatedContext.HandledResponse) { - Logger.LogVerbose(Resources.OIDCH_0008_SecurityTokenReceivedContextHandledResponse); + Logger.LogVerbose("AuthenticationValidated.HandledResponse"); } - else if (securityTokenReceivedContext.Skipped) + else if (authenticationValidatedContext.Skipped) { - Logger.LogVerbose(Resources.OIDCH_0009_SecurityTokenReceivedContextSkipped); + Logger.LogVerbose("AuthenticationValidated.Skipped"); } - return securityTokenReceivedContext; + return authenticationValidatedContext; } - private async Task RunSecurityTokenValidatedEventAsync(OpenIdConnectMessage message, AuthenticationTicket ticket) + private async Task RunUserInformationReceivedEventAsync(AuthenticationTicket ticket, OpenIdConnectMessage message, JObject user) { - var securityTokenValidatedContext = new SecurityTokenValidatedContext(Context, Options) + Logger.LogDebug("User information received:" + user.ToString()); + + var userInformationReceivedContext = new UserInformationReceivedContext(Context, Options) { AuthenticationTicket = ticket, - ProtocolMessage = message + ProtocolMessage = message, + User = user, }; - await Options.Events.SecurityTokenValidated(securityTokenValidatedContext); - if (securityTokenValidatedContext.HandledResponse) + await Options.Events.UserInformationReceived(userInformationReceivedContext); + if (userInformationReceivedContext.HandledResponse) { - Logger.LogVerbose(Resources.OIDCH_0012_SecurityTokenValidatedContextHandledResponse); + Logger.LogVerbose("The UserInformationReceived event returned Handled."); } - else if (securityTokenValidatedContext.Skipped) + else if (userInformationReceivedContext.Skipped) { - Logger.LogVerbose(Resources.OIDCH_0013_SecurityTokenValidatedContextSkipped); + Logger.LogVerbose("The UserInformationReceived event returned Skipped."); } - return securityTokenValidatedContext; + return userInformationReceivedContext; } private async Task RunAuthenticationFailedEventAsync(OpenIdConnectMessage message, Exception exception) @@ -980,7 +1005,6 @@ private AuthenticationTicket ValidateToken(string idToken, OpenIdConnectMessage ticket.Properties.Items[OpenIdConnectSessionProperties.CheckSessionIFrame] = _configuration.CheckSessionIframe; } - // Rename? if (Options.UseTokenLifetime) { var issued = validatedToken.ValidFrom; @@ -1024,14 +1048,33 @@ private async Task ValidateOpenIdConnectProtocolAsync(JwtSecurityToken jwt, Open /// True if the request was handled, false if the next middleware should be invoked. public override Task InvokeAsync() { - return InvokeReplyPathAsync(); + return InvokeReturnPathAsync(); } - private async Task InvokeReplyPathAsync() + private async Task InvokeReturnPathAsync() { var ticket = await HandleAuthenticateOnceAsync(); if (ticket != null) { + Logger.LogDebug("Authentication completed."); + + var authenticationCompletedContext = new AuthenticationCompletedContext(Context, Options) + { + AuthenticationTicket = ticket, + }; + await Options.Events.AuthenticationCompleted(authenticationCompletedContext); + if (authenticationCompletedContext.HandledResponse) + { + Logger.LogVerbose("The AuthenticationCompleted event returned Handled."); + return true; + } + else if (authenticationCompletedContext.Skipped) + { + Logger.LogVerbose("The AuthenticationCompleted event returned Skipped."); + return false; + } + ticket = authenticationCompletedContext.AuthenticationTicket; + if (ticket.Principal != null) { await Request.HttpContext.Authentication.SignInAsync(Options.SignInScheme, ticket.Principal, ticket.Properties); diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectTokenEndpointResponse.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectTokenEndpointResponse.cs index 4be49909e..4216e1eab 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectTokenEndpointResponse.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/OpenIdConnectTokenEndpointResponse.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +// 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.IdentityModel.Protocols.OpenIdConnect; using Newtonsoft.Json.Linq; @@ -15,7 +14,7 @@ public class OpenIdConnectTokenEndpointResponse public OpenIdConnectTokenEndpointResponse(JObject jsonResponse) { JsonResponse = jsonResponse; - Message = new OpenIdConnectMessage() + ProtocolMessage = new OpenIdConnectMessage() { AccessToken = JsonResponse.Value(OpenIdConnectParameterNames.AccessToken), IdToken = JsonResponse.Value(OpenIdConnectParameterNames.IdToken), @@ -27,7 +26,7 @@ public OpenIdConnectTokenEndpointResponse(JObject jsonResponse) /// /// OpenIdConnect message that contains the id token and access tokens /// - public OpenIdConnectMessage Message { get; set; } + public OpenIdConnectMessage ProtocolMessage { get; set; } /// /// Json response returned from the token endpoint diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Properties/Resources.Designer.cs index 7a328601f..5e7b7154c 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Properties/Resources.Designer.cs @@ -186,38 +186,6 @@ internal static string FormatOIDCH_0033_NonceAlreadyExists(object p0) return string.Format(CultureInfo.CurrentCulture, GetString("OIDCH_0033_NonceAlreadyExists"), p0); } - /// - /// OIDCH_0034: RedirectToIdentityProviderContext.HandledResponse - /// - internal static string OIDCH_0034_RedirectToIdentityProviderContextHandledResponse - { - get { return GetString("OIDCH_0034_RedirectToIdentityProviderContextHandledResponse"); } - } - - /// - /// OIDCH_0034: RedirectToIdentityProviderContext.HandledResponse - /// - internal static string FormatOIDCH_0034_RedirectToIdentityProviderContextHandledResponse() - { - return GetString("OIDCH_0034_RedirectToIdentityProviderContextHandledResponse"); - } - - /// - /// OIDCH_0035: RedirectToIdentityProviderContext.Skipped - /// - internal static string OIDCH_0035_RedirectToIdentityProviderContextSkipped - { - get { return GetString("OIDCH_0035_RedirectToIdentityProviderContextSkipped"); } - } - - /// - /// OIDCH_0035: RedirectToIdentityProviderContext.Skipped - /// - internal static string FormatOIDCH_0035_RedirectToIdentityProviderContextSkipped() - { - return GetString("OIDCH_0035_RedirectToIdentityProviderContextSkipped"); - } - /// /// OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: '{0}'. /// @@ -426,38 +394,6 @@ internal static string FormatOIDCH_0007_UpdatingConfiguration() return GetString("OIDCH_0007_UpdatingConfiguration"); } - /// - /// OIDCH_0008: SecurityTokenReceivedContext.HandledResponse - /// - internal static string OIDCH_0008_SecurityTokenReceivedContextHandledResponse - { - get { return GetString("OIDCH_0008_SecurityTokenReceivedContextHandledResponse"); } - } - - /// - /// OIDCH_0008: SecurityTokenReceivedContext.HandledResponse - /// - internal static string FormatOIDCH_0008_SecurityTokenReceivedContextHandledResponse() - { - return GetString("OIDCH_0008_SecurityTokenReceivedContextHandledResponse"); - } - - /// - /// OIDCH_0009: SecurityTokenReceivedContext.Skipped - /// - internal static string OIDCH_0009_SecurityTokenReceivedContextSkipped - { - get { return GetString("OIDCH_0009_SecurityTokenReceivedContextSkipped"); } - } - - /// - /// OIDCH_0009: SecurityTokenReceivedContext.Skipped - /// - internal static string FormatOIDCH_0009_SecurityTokenReceivedContextSkipped() - { - return GetString("OIDCH_0009_SecurityTokenReceivedContextSkipped"); - } - /// /// OIDCH_0010: Validated Security Token must be a JwtSecurityToken was: '{0}'. /// @@ -490,38 +426,6 @@ internal static string FormatOIDCH_0011_UnableToValidateToken(object p0) return string.Format(CultureInfo.CurrentCulture, GetString("OIDCH_0011_UnableToValidateToken"), p0); } - /// - /// OIDCH_0012: SecurityTokenValidatedContext.HandledResponse - /// - internal static string OIDCH_0012_SecurityTokenValidatedContextHandledResponse - { - get { return GetString("OIDCH_0012_SecurityTokenValidatedContextHandledResponse"); } - } - - /// - /// OIDCH_0012: SecurityTokenValidatedContext.HandledResponse - /// - internal static string FormatOIDCH_0012_SecurityTokenValidatedContextHandledResponse() - { - return GetString("OIDCH_0012_SecurityTokenValidatedContextHandledResponse"); - } - - /// - /// OIDCH_0013: SecurityTokenValidatedContext.Skipped - /// - internal static string OIDCH_0013_SecurityTokenValidatedContextSkipped - { - get { return GetString("OIDCH_0013_SecurityTokenValidatedContextSkipped"); } - } - - /// - /// OIDCH_0013: SecurityTokenValidatedContext.Skipped - /// - internal static string FormatOIDCH_0013_SecurityTokenValidatedContextSkipped() - { - return GetString("OIDCH_0013_SecurityTokenValidatedContextSkipped"); - } - /// /// OIDCH_0014: AuthorizationCode received: '{0}'. /// @@ -666,22 +570,6 @@ internal static string FormatOIDCH_0041_Subject_Claim_Not_Found(object p0) return string.Format(CultureInfo.CurrentCulture, GetString("OIDCH_0041_Subject_Claim_Not_Found"), p0); } - /// - /// OIDCH_0042: Authorization Code redeemed: '{0}' - /// - internal static string OIDCH_0042_AuthorizationCodeRedeemed - { - get { return GetString("OIDCH_0042_AuthorizationCodeRedeemed"); } - } - - /// - /// OIDCH_0042: Authorization Code redeemed: '{0}' - /// - internal static string FormatOIDCH_0042_AuthorizationCodeRedeemed(object p0) - { - return string.Format(CultureInfo.CurrentCulture, GetString("OIDCH_0042_AuthorizationCodeRedeemed"), p0); - } - /// /// OIDCH_0043: AuthorizationCodeRedeemedContext.HandledResponse /// diff --git a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx index 792e317a6..64de5d6a2 100644 --- a/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx +++ b/src/Microsoft.AspNet.Authentication.OpenIdConnect/Resources.resx @@ -150,12 +150,6 @@ OIDCH_0033: ProtocolValidator.RequireNonce == true. The generated nonce already exists: this usually indicates the nonce is not unique or has been used. The nonce is: '{0}'. - - OIDCH_0034: RedirectToIdentityProviderContext.HandledResponse - - - OIDCH_0035: RedirectToIdentityProviderContext.Skipped - OIDCH_0036: Uri.IsWellFormedUriString(redirectUri, UriKind.Absolute) returned 'false', redirectUri is: '{0}'. @@ -195,24 +189,12 @@ OIDCH_0007: Updating configuration - - OIDCH_0008: SecurityTokenReceivedContext.HandledResponse - - - OIDCH_0009: SecurityTokenReceivedContext.Skipped - OIDCH_0010: Validated Security Token must be a JwtSecurityToken was: '{0}'. OIDCH_0011: Unable to validate the 'id_token', no suitable ISecurityTokenValidator was found for: '{0}'." - - OIDCH_0012: SecurityTokenValidatedContext.HandledResponse - - - OIDCH_0013: SecurityTokenValidatedContext.Skipped - OIDCH_0014: AuthorizationCode received: '{0}'. @@ -240,9 +222,6 @@ OIDCH_0041: Subject claim not found in {0}. - - OIDCH_0042: Authorization Code redeemed: '{0}' - OIDCH_0043: AuthorizationCodeRedeemedContext.HandledResponse diff --git a/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs b/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs index db1a7e3a2..711e0f4b7 100644 --- a/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs +++ b/test/Microsoft.AspNet.Authentication.Test/AuthenticationHandlerFacts.cs @@ -5,13 +5,11 @@ using System.Collections.Generic; using System.IO; using System.Threading.Tasks; -using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Features.Authentication; using Microsoft.AspNet.Http.Internal; using Microsoft.Framework.Logging; using Microsoft.Framework.Primitives; -using Moq; using Xunit; namespace Microsoft.AspNet.Authentication diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs index 2fbab74cf..f9cd88d57 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerForTestingAuthenticate.cs @@ -33,7 +33,7 @@ protected override Task RedeemAuthorizationC return Task.FromResult(new OpenIdConnectTokenEndpointResponse(jsonResponse)); } - protected override Task GetUserInformationAsync(AuthenticationProperties properties, OpenIdConnectMessage message, AuthenticationTicket ticket) + protected override Task GetUserInformationAsync(OpenIdConnectMessage message, AuthenticationTicket ticket) { var claimsIdentity = (ClaimsIdentity)ticket.Principal.Identity; if (claimsIdentity == null) diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs index 4822ffbf5..d2bee5035 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectHandlerTests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Linq; @@ -19,7 +18,6 @@ using Microsoft.Framework.OptionsModel; using Microsoft.Framework.WebEncoders; using Microsoft.IdentityModel.Protocols.OpenIdConnect; -using Moq; using Xunit; namespace Microsoft.AspNet.Authentication.Tests.OpenIdConnect @@ -56,7 +54,7 @@ public void LoggingLevel() Assert.False(logger.IsEnabled(LogLevel.Warning)); } - [Theory, MemberData("AuthenticateCoreStateDataSet")] + [Theory, MemberData(nameof(AuthenticateCoreStateDataSet))] public async Task AuthenticateCoreState(Action action, OpenIdConnectMessage message) { var handler = new OpenIdConnectHandlerForTestingAuthenticate(); @@ -103,7 +101,7 @@ private static void SetStateOptions(OpenIdConnectOptions options) options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); options.Events = new OpenIdConnectEvents() { - OnAuthorizationCodeRedeemed = context => + OnTokenResponseReceived = context => { context.HandleResponse(); if (context.ProtocolMessage.State == null && !context.ProtocolMessage.Parameters.ContainsKey(ExpectedStateParameter)) @@ -118,271 +116,6 @@ private static void SetStateOptions(OpenIdConnectOptions options) }; } -#region Configure Options for AuthenticateCore variations - - private static void DefaultOptions(OpenIdConnectOptions options) - { - options.AuthenticationScheme = "OpenIdConnectHandlerTest"; - options.SignInScheme = "OpenIdConnectHandlerTest"; - options.ConfigurationManager = TestUtilities.DefaultOpenIdConnectConfigurationManager; - options.ClientId = Guid.NewGuid().ToString(); - options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); - } - - private static void AuthorizationCodeReceivedHandledOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.SecurityTokenValidator = MockSecurityTokenValidator(); - options.ProtocolValidator = MockProtocolValidator(); - options.Events = new OpenIdConnectEvents() - { - OnAuthorizationCodeReceived = (context) => - { - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - - private static void AuthorizationCodeReceivedSkippedOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.SecurityTokenValidator = MockSecurityTokenValidator(); - options.ProtocolValidator = MockProtocolValidator(); - options.Events = new OpenIdConnectEvents() - { - OnAuthorizationCodeReceived = (context) => - { - context.SkipToNextMiddleware(); - return Task.FromResult(null); - } - }; - } - - private static void AuthenticationErrorHandledOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.SecurityTokenValidator = MockSecurityTokenValidator(); - options.ProtocolValidator = MockProtocolValidator(); - options.Events = new OpenIdConnectEvents() - { - OnAuthenticationFailed = (context) => - { - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - - private static void AuthenticationErrorSkippedOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.SecurityTokenValidator = MockSecurityTokenValidator(); - options.ProtocolValidator = MockProtocolValidator(); - options.Events = new OpenIdConnectEvents() - { - OnAuthenticationFailed = (context) => - { - context.SkipToNextMiddleware(); - return Task.FromResult(null); - } - }; - } - - private static void MessageReceivedHandledOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.Events = new OpenIdConnectEvents() - { - OnMessageReceived = (context) => - { - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - - private static void CodeReceivedAndRedeemedHandledOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.ResponseType = OpenIdConnectResponseTypes.Code; - options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); - options.Events = new OpenIdConnectEvents() - { - OnAuthorizationCodeRedeemed = (context) => - { - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - - private static void CodeReceivedAndRedeemedSkippedOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.ResponseType = OpenIdConnectResponseTypes.Code; - options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); - options.Events = new OpenIdConnectEvents() - { - OnAuthorizationCodeRedeemed = (context) => - { - context.SkipToNextMiddleware(); - return Task.FromResult(null); - } - }; - } - - private static void GetUserInfoFromUIEndpoint(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.ResponseType = OpenIdConnectResponseTypes.Code; - options.ProtocolValidator.RequireNonce = false; - options.StateDataFormat = new AuthenticationPropertiesFormaterKeyValue(); - options.GetClaimsFromUserInfoEndpoint = true; - options.SecurityTokenValidator = MockSecurityTokenValidator(); - options.Events = new OpenIdConnectEvents() - { - OnSecurityTokenValidated = (context) => - { - var claimValue = context.AuthenticationTicket.Principal.FindFirst("test claim"); - Assert.Equal(claimValue.Value, "test value"); - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - private static void MessageReceivedSkippedOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.Events = new OpenIdConnectEvents() - { - OnMessageReceived = (context) => - { - context.SkipToNextMiddleware(); - return Task.FromResult(null); - } - }; - } - - private static void MessageWithErrorOptions(OpenIdConnectOptions options) - { - AuthenticationErrorHandledOptions(options); - } - - private static void SecurityTokenReceivedHandledOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.Events = new OpenIdConnectEvents() - { - OnSecurityTokenReceived = (context) => - { - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - - private static void SecurityTokenReceivedSkippedOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.Events = new OpenIdConnectEvents() - { - OnSecurityTokenReceived = (context) => - { - context.SkipToNextMiddleware(); - return Task.FromResult(null); - } - }; - } - - private static ISecurityTokenValidator MockSecurityTokenValidator() - { - var mockValidator = new Mock(); - mockValidator.Setup(v => v.ValidateToken(It.IsAny(), It.IsAny(), out specCompliantJwt)).Returns(new ClaimsPrincipal()); - mockValidator.Setup(v => v.CanReadToken(It.IsAny())).Returns(true); - return mockValidator.Object; - } - - private static OpenIdConnectProtocolValidator MockProtocolValidator() - { - var mockProtocolValidator = new Mock(); - mockProtocolValidator.Setup(v => v.Validate(It.IsAny(), It.IsAny())); - return mockProtocolValidator.Object; - } - - private static void SecurityTokenValidatorCannotReadToken(OpenIdConnectOptions options) - { - AuthenticationErrorHandledOptions(options); - var mockValidator = new Mock(); - SecurityToken jwt = null; - mockValidator.Setup(v => v.ValidateToken(It.IsAny(), It.IsAny(), out jwt)).Returns(new ClaimsPrincipal()); - mockValidator.Setup(v => v.CanReadToken(It.IsAny())).Returns(false); - options.SecurityTokenValidator = mockValidator.Object; - } - - private static void SecurityTokenValidatorThrows(OpenIdConnectOptions options) - { - AuthenticationErrorHandledOptions(options); - var mockValidator = new Mock(); - SecurityToken jwt = null; - mockValidator.Setup(v => v.ValidateToken(It.IsAny(), It.IsAny(), out jwt)).Throws(); - mockValidator.Setup(v => v.CanReadToken(It.IsAny())).Returns(true); - options.SecurityTokenValidator = mockValidator.Object; - } - - private static void SecurityTokenValidatorValidatesAllTokens(OpenIdConnectOptions options) - { - DefaultOptions(options); - options.SecurityTokenValidator = MockSecurityTokenValidator(); - options.ProtocolValidator.RequireTimeStampInNonce = false; - options.ProtocolValidator.RequireNonce = false; - } - - private static void SecurityTokenValidatedHandledOptions(OpenIdConnectOptions options) - { - SecurityTokenValidatorValidatesAllTokens(options); - options.Events = new OpenIdConnectEvents() - { - OnSecurityTokenValidated = (context) => - { - context.HandleResponse(); - return Task.FromResult(null); - } - }; - } - - private static void SecurityTokenValidatedSkippedOptions(OpenIdConnectOptions options) - { - SecurityTokenValidatorValidatesAllTokens(options); - options.Events = new OpenIdConnectEvents() - { - OnSecurityTokenValidated = (context) => - { - context.SkipToNextMiddleware(); - return Task.FromResult(null); - } - }; - } - - private static void StateNullOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - } - - private static void StateEmptyOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - } - - private static void StateInvalidOptions(OpenIdConnectOptions options) - { - DefaultOptions(options); - } - -#endregion - - private static Task EmptyTask() { return Task.FromResult(0); } - private static TestServer CreateServer(ConfigureOptions options, IUrlEncoder encoder, OpenIdConnectHandler handler = null) { return TestServer.Create( @@ -401,24 +134,5 @@ private static TestServer CreateServer(ConfigureOptions op } ); } - - private static TestServer CreateServer(ConfigureOptions configureOptions, IUrlEncoder encoder, ILoggerFactory loggerFactory, OpenIdConnectHandler handler = null) - { - return TestServer.Create( - app => - { - app.UseMiddleware(configureOptions, encoder, loggerFactory, handler); - app.Use(async (context, next) => - { - await next(); - }); - }, - services => - { - services.AddWebEncoders(); - services.AddDataProtection(); - } - ); - } } } diff --git a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs index eb1d70577..867531930 100644 --- a/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/OpenIdConnect/OpenIdConnectMiddlewareTests.cs @@ -106,30 +106,49 @@ public async Task ChallengeWillUseOptionsProperties() } /// - /// Tests RedirectToIdentityProviderContext replaces the OpenIdConnectMesssage correctly. + /// Tests RedirectForAuthenticationContext replaces the OpenIdConnectMesssage correctly. /// /// Task - [Theory] - [InlineData(Challenge, OpenIdConnectRequestType.AuthenticationRequest)] - [InlineData(Signout, OpenIdConnectRequestType.LogoutRequest)] - public async Task ChallengeSettingMessage(string challenge, OpenIdConnectRequestType requestType) + [Fact] + public async Task ChallengeSettingMessage() { var configuration = new OpenIdConnectConfiguration { AuthorizationEndpoint = ExpectedAuthorizeRequest, - EndSessionEndpoint = ExpectedLogoutRequest }; var queryValues = new ExpectedQueryValues(DefaultAuthority, configuration) { - RequestType = requestType + RequestType = OpenIdConnectRequestType.AuthenticationRequest }; var server = CreateServer(SetProtocolMessageOptions); - var transaction = await SendAsync(server, DefaultHost + challenge); + var transaction = await SendAsync(server, DefaultHost + Challenge); Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, new string[] {}); } + /// + /// Tests RedirectForSignOutContext replaces the OpenIdConnectMesssage correctly. + /// + /// Task + [Fact] + public async Task SignOutSettingMessage() + { + var configuration = new OpenIdConnectConfiguration + { + EndSessionEndpoint = ExpectedLogoutRequest + }; + + var queryValues = new ExpectedQueryValues(DefaultAuthority, configuration) + { + RequestType = OpenIdConnectRequestType.LogoutRequest + }; + var server = CreateServer(SetProtocolMessageOptions); + var transaction = await SendAsync(server, DefaultHost + Signout); + Assert.Equal(HttpStatusCode.Redirect, transaction.Response.StatusCode); + queryValues.CheckValues(transaction.Response.Headers.Location.AbsoluteUri, new string[] { }); + } + private static void SetProtocolMessageOptions(OpenIdConnectOptions options) { var mockOpenIdConnectMessage = new Mock(); @@ -138,7 +157,12 @@ private static void SetProtocolMessageOptions(OpenIdConnectOptions options) options.AutomaticAuthentication = true; options.Events = new OpenIdConnectEvents() { - OnRedirectToIdentityProvider = (context) => + OnRedirectToAuthenticationEndpoint = (context) => + { + context.ProtocolMessage = mockOpenIdConnectMessage.Object; + return Task.FromResult(null); + }, + OnRedirectToEndSessionEndpoint = (context) => { context.ProtocolMessage = mockOpenIdConnectMessage.Object; return Task.FromResult(null); @@ -170,7 +194,7 @@ public async Task ChallengeSettingState(string userState, string challenge) options.AutomaticAuthentication = challenge.Equals(ChallengeWithOutContext); options.Events = new OpenIdConnectEvents() { - OnRedirectToIdentityProvider = context => + OnRedirectToAuthenticationEndpoint = context => { context.ProtocolMessage.State = userState; return Task.FromResult(null); @@ -221,7 +245,7 @@ public async Task ChallengeWillUseEvents() SetOptions(options, DefaultParameters(), queryValues); options.Events = new OpenIdConnectEvents() { - OnRedirectToIdentityProvider = context => + OnRedirectToAuthenticationEndpoint = context => { context.ProtocolMessage.ClientId = queryValuesSetInEvent.ClientId; context.ProtocolMessage.RedirectUri = queryValuesSetInEvent.RedirectUri; @@ -344,7 +368,7 @@ private static TestServer CreateServer(Action configureOpt { app.UseCookieAuthentication(options => { - options.AuthenticationScheme = OpenIdConnectDefaults.AuthenticationScheme; + options.AuthenticationScheme = CookieAuthenticationDefaults.AuthenticationScheme; }); app.UseOpenIdConnectAuthentication(configureOptions); app.Use(async (context, next) =>