/// <summary> /// Invoked if exceptions are thrown during request processing. The exceptions will be re-thrown after this event unless suppressed. /// </summary> public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context);
/// <summary> /// Invoked to process incoming authentication messages. /// </summary> /// <returns></returns> protected override async Task <HandleRequestResult> HandleRemoteAuthenticateAsync() { WsFederationMessage? wsFederationMessage = null; AuthenticationProperties?properties = null; // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. if (HttpMethods.IsPost(Request.Method) && !string.IsNullOrEmpty(Request.ContentType) // May have media/type; charset=utf-8, allow partial match. && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) && Request.Body.CanRead) { var form = await Request.ReadFormAsync(Context.RequestAborted); // ToArray handles the StringValues.IsNullOrEmpty case. We assume non-empty Value does not contain null elements. #pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. wsFederationMessage = new WsFederationMessage(form.Select(pair => new KeyValuePair <string, string[]>(pair.Key, pair.Value.ToArray()))); #pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. } if (wsFederationMessage == null || !wsFederationMessage.IsSignInMessage) { if (Options.SkipUnrecognizedRequests) { // Not for us? return(HandleRequestResult.SkipHandler()); } return(HandleRequestResult.Fail("No message.")); } try { // Retrieve our cached redirect uri var state = wsFederationMessage.Wctx; // WsFed allows for uninitiated logins, state may be missing. See AllowUnsolicitedLogins. properties = Options.StateDataFormat.Unprotect(state); if (properties == null) { if (!Options.AllowUnsolicitedLogins) { return(HandleRequestResult.Fail("Unsolicited logins are not allowed.")); } } else { // Extract the user state from properties and reset. properties.Items.TryGetValue(WsFederationDefaults.UserstatePropertiesKey, out var userState); wsFederationMessage.Wctx = userState; } var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options, properties) { ProtocolMessage = wsFederationMessage }; await Events.MessageReceived(messageReceivedContext); if (messageReceivedContext.Result != null) { return(messageReceivedContext.Result); } wsFederationMessage = messageReceivedContext.ProtocolMessage; properties = messageReceivedContext.Properties !; // Provides a new instance if not set. // If state did flow from the challenge then validate it. See AllowUnsolicitedLogins above. if (properties.Items.TryGetValue(CorrelationProperty, out string?correlationId) && !ValidateCorrelationId(properties)) { return(HandleRequestResult.Fail("Correlation failed.", properties)); } if (wsFederationMessage.Wresult == null) { Logger.SignInWithoutWResult(); return(HandleRequestResult.Fail(Resources.SignInMessageWresultIsMissing, properties)); } var token = wsFederationMessage.GetToken(); if (string.IsNullOrEmpty(token)) { Logger.SignInWithoutToken(); return(HandleRequestResult.Fail(Resources.SignInMessageTokenIsMissing, properties)); } var securityTokenReceivedContext = new SecurityTokenReceivedContext(Context, Scheme, Options, properties) { ProtocolMessage = wsFederationMessage }; await Events.SecurityTokenReceived(securityTokenReceivedContext); if (securityTokenReceivedContext.Result != null) { return(securityTokenReceivedContext.Result); } wsFederationMessage = securityTokenReceivedContext.ProtocolMessage; properties = messageReceivedContext.Properties !; if (_configuration == null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } // Copy and augment to avoid cross request race conditions for updated configurations. var tvp = Options.TokenValidationParameters.Clone(); var issuers = new[] { _configuration.Issuer }; tvp.ValidIssuers = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers)); tvp.IssuerSigningKeys = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys)); ClaimsPrincipal?principal = null; SecurityToken? parsedToken = null; foreach (var validator in Options.SecurityTokenHandlers) { if (validator.CanReadToken(token)) { principal = validator.ValidateToken(token, tvp, out parsedToken); break; } } if (principal == null) { throw new SecurityTokenException(Resources.Exception_NoTokenValidatorFound); } if (Options.UseTokenLifetime && parsedToken != null) { // Override any session persistence to match the token lifetime. var issued = parsedToken.ValidFrom; if (issued != DateTime.MinValue) { properties.IssuedUtc = issued.ToUniversalTime(); } var expires = parsedToken.ValidTo; if (expires != DateTime.MinValue) { properties.ExpiresUtc = expires.ToUniversalTime(); } properties.AllowRefresh = false; } var securityTokenValidatedContext = new SecurityTokenValidatedContext(Context, Scheme, Options, principal, properties) { ProtocolMessage = wsFederationMessage, SecurityToken = parsedToken, }; await Events.SecurityTokenValidated(securityTokenValidatedContext); if (securityTokenValidatedContext.Result != null) { return(securityTokenValidatedContext.Result); } // Flow possible changes principal = securityTokenValidatedContext.Principal !; properties = securityTokenValidatedContext.Properties; return(HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name))); } catch (Exception exception) { Logger.ExceptionProcessingMessage(exception); // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification. if (Options.RefreshOnIssuerKeyNotFound && exception is SecurityTokenSignatureKeyNotFoundException) { Options.ConfigurationManager.RequestRefresh(); } var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options) { ProtocolMessage = wsFederationMessage, Exception = exception }; await Events.AuthenticationFailed(authenticationFailedContext); if (authenticationFailedContext.Result != null) { return(authenticationFailedContext.Result); } return(HandleRequestResult.Fail(exception, properties)); } }
/// <summary> /// Invoked to process incoming authentication messages. /// </summary> /// <returns></returns> protected override async Task <HandleRequestResult> HandleRemoteAuthenticateAsync() { WsFederationMessage wsFederationMessage = null; // assumption: if the ContentType is "application/x-www-form-urlencoded" it should be safe to read as it is small. if (HttpMethods.IsPost(Request.Method) && !string.IsNullOrWhiteSpace(Request.ContentType) // May have media/type; charset=utf-8, allow partial match. && Request.ContentType.StartsWith("application/x-www-form-urlencoded", StringComparison.OrdinalIgnoreCase) && Request.Body.CanRead) { var form = await Request.ReadFormAsync(); wsFederationMessage = new WsFederationMessage(form.Select(pair => new KeyValuePair <string, string[]>(pair.Key, pair.Value))); } if (wsFederationMessage == null || !wsFederationMessage.IsSignInMessage) { if (Options.SkipUnrecognizedRequests) { // Not for us? return(HandleRequestResult.SkipHandler()); } return(HandleRequestResult.Fail("No message.")); } try { // Retrieve our cached redirect uri string state = wsFederationMessage.Wctx; // WsFed allows for uninitiated logins, state may be missing. var properties = GetPropertiesFromWctx(state); var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options, properties) { ProtocolMessage = wsFederationMessage }; await Options.Events.MessageReceived(messageReceivedContext); if (messageReceivedContext.Result != null) { return(messageReceivedContext.Result); } if (wsFederationMessage.Wresult == null) { Logger.LogWarning("Received a sign-in message without a WResult."); return((HandleRequestResult)HandleRequestResult.NoResult()); } string token = wsFederationMessage.GetToken(); if (string.IsNullOrWhiteSpace(token)) { Logger.LogWarning("Received a sign-in message without a token."); return((HandleRequestResult)HandleRequestResult.NoResult()); } var securityTokenReceivedContext = new SecurityTokenReceivedContext(Context, Scheme, Options, properties) { ProtocolMessage = wsFederationMessage }; await Options.Events.SecurityTokenReceived(securityTokenReceivedContext); if (securityTokenReceivedContext.Result != null) { return(securityTokenReceivedContext.Result); } if (_configuration == null) { _configuration = await Options.ConfigurationManager.GetConfigurationAsync(Context.RequestAborted); } // Copy and augment to avoid cross request race conditions for updated configurations. var tvp = Options.TokenValidationParameters.Clone(); IEnumerable <string> issuers = new[] { _configuration.Issuer }; tvp.ValidIssuers = (tvp.ValidIssuers == null ? issuers : tvp.ValidIssuers.Concat(issuers)); tvp.IssuerSigningKeys = (tvp.IssuerSigningKeys == null ? _configuration.SigningKeys : tvp.IssuerSigningKeys.Concat(_configuration.SigningKeys)); ClaimsPrincipal principal = null; SecurityToken parsedToken = null; foreach (var validator in Options.SecurityTokenHandlers) { if (validator.CanReadToken(token)) { principal = validator.ValidateToken(token, tvp, out parsedToken); break; } } if (principal == null) { throw new SecurityTokenException("no validator found"); } if (Options.UseTokenLifetime) { // Override any session persistence to match the token lifetime. DateTime issued = parsedToken.ValidFrom; if (issued != DateTime.MinValue) { properties.IssuedUtc = issued.ToUniversalTime(); } DateTime expires = parsedToken.ValidTo; if (expires != DateTime.MinValue) { properties.ExpiresUtc = expires.ToUniversalTime(); } properties.AllowRefresh = false; } var securityTokenValidatedContext = new SecurityTokenValidatedContext(Context, Scheme, Options, principal, properties) { ProtocolMessage = wsFederationMessage, }; await Options.Events.SecurityTokenValidated(securityTokenValidatedContext); if (securityTokenValidatedContext.Result != null) { return(securityTokenValidatedContext.Result); } // Flow possible changes principal = securityTokenValidatedContext.Principal; properties = securityTokenValidatedContext.Properties; return(HandleRequestResult.Success(new AuthenticationTicket(principal, properties, Scheme.Name))); } catch (Exception exception) { Logger.LogError("Exception occurred while processing message: ", exception); // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the notification. if (Options.RefreshOnIssuerKeyNotFound && exception.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException))) { Options.ConfigurationManager.RequestRefresh(); } var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options) { ProtocolMessage = wsFederationMessage, Exception = exception }; await Options.Events.AuthenticationFailed(authenticationFailedContext); if (authenticationFailedContext.Result != null) { return(authenticationFailedContext.Result); } throw; } }