diff --git a/src/Microsoft.AspNet.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Microsoft.AspNet.Authentication.JwtBearer/JwtBearerHandler.cs index 79ca1a084d..beb77486f4 100644 --- a/src/Microsoft.AspNet.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Microsoft.AspNet.Authentication.JwtBearer/JwtBearerHandler.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.IdentityModel.Tokens; using System.Linq; +using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNet.Http.Authentication; using Microsoft.AspNet.Http.Features.Authentication; @@ -34,7 +36,6 @@ namespace Microsoft.AspNet.Authentication.JwtBearer { return AuthenticateResult.Success(receivingTokenContext.AuthenticationTicket); } - if (receivingTokenContext.Skipped) { return AuthenticateResult.Success(ticket: null); @@ -76,7 +77,6 @@ namespace Microsoft.AspNet.Authentication.JwtBearer { return AuthenticateResult.Success(receivedTokenContext.AuthenticationTicket); } - if (receivedTokenContext.Skipped) { return AuthenticateResult.Success(ticket: null); @@ -103,12 +103,37 @@ namespace Microsoft.AspNet.Authentication.JwtBearer validationParameters.IssuerSigningKeys = (validationParameters.IssuerSigningKeys == null ? _configuration.SigningKeys : validationParameters.IssuerSigningKeys.Concat(_configuration.SigningKeys)); } + List validationFailures = null; SecurityToken validatedToken; foreach (var validator in Options.SecurityTokenValidators) { if (validator.CanReadToken(token)) { - var principal = validator.ValidateToken(token, validationParameters, out validatedToken); + ClaimsPrincipal principal; + try + { + principal = validator.ValidateToken(token, validationParameters, out validatedToken); + } + catch (Exception ex) + { + Logger.LogInformation("Failed to validate the token: " + token, 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 && ex.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException))) + { + Options.ConfigurationManager.RequestRefresh(); + } + + if (validationFailures == null) + { + validationFailures = new List(1); + } + validationFailures.Add(ex); + continue; + } + + Logger.LogInformation("Successfully validated the token"); + var ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), Options.AuthenticationScheme); var validatedTokenContext = new ValidatedTokenContext(Context, Options) { @@ -120,7 +145,6 @@ namespace Microsoft.AspNet.Authentication.JwtBearer { return AuthenticateResult.Success(validatedTokenContext.AuthenticationTicket); } - if (validatedTokenContext.Skipped) { return AuthenticateResult.Success(ticket: null); @@ -130,19 +154,32 @@ namespace Microsoft.AspNet.Authentication.JwtBearer } } - // REVIEW: this maybe return an error instead? - throw new InvalidOperationException("No SecurityTokenValidator available for token: " + token ?? "null"); + if (validationFailures != null) + { + var authenticationFailedContext = new AuthenticationFailedContext(Context, Options) + { + Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures) + }; + + await Options.Events.AuthenticationFailed(authenticationFailedContext); + if (authenticationFailedContext.HandledResponse) + { + return AuthenticateResult.Success(authenticationFailedContext.AuthenticationTicket); + } + if (authenticationFailedContext.Skipped) + { + return AuthenticateResult.Success(ticket: null); + } + + return AuthenticateResult.Failed(authenticationFailedContext.Exception); + } + + return AuthenticateResult.Failed("No SecurityTokenValidator available for token: " + token ?? "[null]"); } catch (Exception ex) { Logger.LogError("Exception occurred while processing message", 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 && ex.GetType().Equals(typeof(SecurityTokenSignatureKeyNotFoundException))) - { - Options.ConfigurationManager.RequestRefresh(); - } - var authenticationFailedContext = new AuthenticationFailedContext(Context, Options) { Exception = ex diff --git a/test/Microsoft.AspNet.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs b/test/Microsoft.AspNet.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs index 17e14d449d..aaef7ffa91 100644 --- a/test/Microsoft.AspNet.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs +++ b/test/Microsoft.AspNet.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs @@ -112,6 +112,34 @@ namespace Microsoft.AspNet.Authentication.JwtBearer Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); } + [Fact] + public async Task UnrecognizedTokenReceived() + { + var server = CreateServer(options => + { + options.AutomaticAuthenticate = true; + }); + + var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); + Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); + Assert.Equal("", response.ResponseText); + } + + [Fact] + public async Task InvalidTokenReceived() + { + var server = CreateServer(options => + { + options.AutomaticAuthenticate = true; + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Add(new InvalidTokenValidator()); + }); + + var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); + Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); + Assert.Equal("", response.ResponseText); + } + [Fact] public async Task CustomTokenReceived() { @@ -284,6 +312,28 @@ namespace Microsoft.AspNet.Authentication.JwtBearer Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); } + class InvalidTokenValidator : ISecurityTokenValidator + { + public InvalidTokenValidator() + { + } + + public bool CanValidateToken => true; + + public int MaximumTokenSizeInBytes + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public bool CanReadToken(string securityToken) => true; + + public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) + { + throw new SecurityTokenException("InvalidToken"); + } + } + class BlobTokenValidator : ISecurityTokenValidator { public BlobTokenValidator(string authenticationScheme)