From 0314632696464efaa9d34d08233e0a292336361d Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Tue, 2 Aug 2016 11:56:31 -0700 Subject: [PATCH] JwtBearer Token: Catch exception during unauthorized flow --- .../CookieAuthenticationHandler.cs | 1 - .../JwtBearerHandler.cs | 4 +- .../AuthenticationHandler.cs | 32 +++++++++- .../JwtBearer/JwtBearerMiddlewareTests.cs | 59 +++++++++++++++++-- 4 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs index eecd03065a..302010429b 100644 --- a/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationHandler.cs @@ -323,7 +323,6 @@ namespace Microsoft.AspNetCore.Authentication.Cookies await Options.Events.RedirectToReturnUrl(redirectContext); } } - } private static bool IsHostRelative(string path) diff --git a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs index 34b13562a5..ee5575251d 100644 --- a/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication.JwtBearer/JwtBearerHandler.cs @@ -185,11 +185,11 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer protected override async Task HandleUnauthorizedAsync(ChallengeContext context) { - var authResult = await HandleAuthenticateOnceAsync(); + var authResult = await HandleAuthenticateOnceSafeAsync(); var eventContext = new JwtBearerChallengeContext(Context, Options, new AuthenticationProperties(context.Properties)) { - AuthenticateFailure = authResult?.Failure, + AuthenticateFailure = authResult?.Failure }; // Avoid returning error=invalid_token if the error is not caused by an authentication failure (e.g missing token). diff --git a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs index 639be0103a..9141e8811d 100644 --- a/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs +++ b/src/Microsoft.AspNetCore.Authentication/AuthenticationHandler.cs @@ -233,15 +233,43 @@ namespace Microsoft.AspNetCore.Authentication } } + /// + /// Handle the authentication for once. + /// + /// If the authentication has been done before returns the last authentication result. + /// protected Task HandleAuthenticateOnceAsync() { if (_authenticateTask == null) { _authenticateTask = HandleAuthenticateAsync(); } + return _authenticateTask; } + /// + /// Handle the authentication for once. + /// + /// If the authentication has been done before returns the last authentication result. + /// This method won't throw exception. Any exception thrown during the authentication will be convert + /// to a AuthenticateResult. + /// + protected Task HandleAuthenticateOnceSafeAsync() + { + try + { + return HandleAuthenticateOnceAsync().ContinueWith( + task => task.IsFaulted ? AuthenticateResult.Fail(task.Exception) : task.Result + ); + } + catch (Exception ex) + { + // capture exception which is thrown before the task is actually started + return Task.FromResult(AuthenticateResult.Fail(ex)); + } + } + protected abstract Task HandleAuthenticateAsync(); public async Task SignInAsync(SignInContext context) @@ -313,7 +341,7 @@ namespace Microsoft.AspNetCore.Authentication { case ChallengeBehavior.Automatic: // If there is a principal already, invoke the forbidden code path - var result = await HandleAuthenticateOnceAsync(); + var result = await HandleAuthenticateOnceSafeAsync(); if (result?.Ticket?.Principal != null) { goto case ChallengeBehavior.Forbidden; @@ -350,4 +378,4 @@ namespace Microsoft.AspNetCore.Authentication auth.Handler = PriorHandler; } } -} \ 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 b1f0ce4fed..fea01b36fe 100644 --- a/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs +++ b/test/Microsoft.AspNetCore.Authentication.Test/JwtBearer/JwtBearerMiddlewareTests.cs @@ -59,6 +59,46 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); } + [Fact] + public async Task ThrowAtAuthenticationFailedEvent() + { + var options = new JwtBearerOptions + { + Events = new JwtBearerEvents + { + OnAuthenticationFailed = context => + { + context.Response.StatusCode = 401; + throw new Exception(); + }, + OnMessageReceived = context => + { + context.Token = "something"; + return Task.FromResult(0); + } + } + }; + options.SecurityTokenValidators.Clear(); + options.SecurityTokenValidators.Insert(0, new InvalidTokenValidator()); + + var server = CreateServer(options, async (context, next) => + { + try + { + await next(); + Assert.False(true, "Expected exception is not thrown"); + } + catch (Exception) + { + context.Response.StatusCode = 401; + await context.Response.WriteAsync("i got this"); + } + }); + + var transaction = await server.SendAsync("https://example.com/signIn"); + + Assert.Equal(HttpStatusCode.Unauthorized, transaction.Response.StatusCode); + } [Fact] public async Task CustomHeaderReceived() @@ -104,7 +144,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public async Task HeaderWithoutBearerReceived() { var server = CreateServer(new JwtBearerOptions()); - var response = await SendAsync(server, "http://example.com/oauth","Token"); + var response = await SendAsync(server, "http://example.com/oauth", "Token"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); } @@ -347,7 +387,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer var response = await SendAsync(server, "http://example.com/unauthorized", "Bearer Token"); Assert.Equal(HttpStatusCode.Forbidden, response.Response.StatusCode); } - + [Fact] public async Task BearerDoesNothingTo401IfNotAuthenticated() { @@ -522,7 +562,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public string AuthenticationScheme { get; } public bool CanValidateToken => true; - + public int MaximumTokenSizeInBytes { get @@ -558,11 +598,21 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } } - private static TestServer CreateServer(JwtBearerOptions options, Func handler = null) + private static TestServer CreateServer(JwtBearerOptions options) + { + return CreateServer(options, handlerBeforeAuth: null); + } + + private static TestServer CreateServer(JwtBearerOptions options, Func, Task> handlerBeforeAuth) { var builder = new WebHostBuilder() .Configure(app => { + if (handlerBeforeAuth != null) + { + app.Use(handlerBeforeAuth); + } + if (options != null) { app.UseJwtBearerAuthentication(options); @@ -622,6 +672,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer }); }) .ConfigureServices(services => services.AddAuthentication()); + return new TestServer(builder); }