using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Microsoft.Net.Http.Headers; using System; using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Text.Encodings.Web; using System.Threading.Tasks; namespace PoweredSoft.DynamicJwtBearer { public class DynamicJwtBearerHandler : JwtBearerHandler { private readonly IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver; public DynamicJwtBearerHandler(IOptionsMonitor options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver) : base(options, logger, encoder, clock) { this.dynamicJwtBearerHanderConfigurationResolver = dynamicJwtBearerHanderConfigurationResolver; } /// /// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using set in the options. /// /// protected override async Task HandleAuthenticateAsync() { string token = null; try { // Give application opportunity to find from a different location, adjust, or reject token var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options); // event can set the token await Events.MessageReceived(messageReceivedContext); if (messageReceivedContext.Result != null) { return messageReceivedContext.Result; } // If application retrieved token from somewhere else, use that. token = messageReceivedContext.Token; if (string.IsNullOrEmpty(token)) { string authorization = Request.Headers[HeaderNames.Authorization]; // If no authorization header found, nothing to process further if (string.IsNullOrEmpty(authorization)) { return AuthenticateResult.NoResult(); } if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase)) { token = authorization.Substring("Bearer ".Length).Trim(); } // If no token found, no further work possible if (string.IsNullOrEmpty(token)) { return AuthenticateResult.NoResult(); } } var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context); var validationParameters = Options.TokenValidationParameters.Clone(); if (currentConfiguration != null) { var issuers = new[] { currentConfiguration.Issuer }; validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers; validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(currentConfiguration.SigningKeys) ?? currentConfiguration.SigningKeys; } List validationFailures = null; SecurityToken validatedToken; foreach (var validator in Options.SecurityTokenValidators) { if (validator.CanReadToken(token)) { ClaimsPrincipal principal; try { principal = validator.ValidateToken(token, validationParameters, out validatedToken); } catch (Exception ex) { Logger.TokenValidationFailed(ex); // Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event. if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null && ex is SecurityTokenSignatureKeyNotFoundException) { Options.ConfigurationManager.RequestRefresh(); } if (validationFailures == null) { validationFailures = new List(1); } validationFailures.Add(ex); continue; } Logger.TokenValidationSucceeded(); var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options) { Principal = principal, SecurityToken = validatedToken }; await Events.TokenValidated(tokenValidatedContext); if (tokenValidatedContext.Result != null) { return tokenValidatedContext.Result; } if (Options.SaveToken) { tokenValidatedContext.Properties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = token } }); } tokenValidatedContext.Success(); return tokenValidatedContext.Result; } } if (validationFailures != null) { var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options) { Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures) }; await Events.AuthenticationFailed(authenticationFailedContext); if (authenticationFailedContext.Result != null) { return authenticationFailedContext.Result; } return AuthenticateResult.Fail(authenticationFailedContext.Exception); } return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]"); } catch (Exception ex) { Logger.ErrorProcessingMessage(ex); var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options) { Exception = ex }; await Events.AuthenticationFailed(authenticationFailedContext); if (authenticationFailedContext.Result != null) { return authenticationFailedContext.Result; } throw; } } } }