diff --git a/src/Security/Authentication/JwtBearer/ref/Microsoft.AspNetCore.Authentication.JwtBearer.netcoreapp3.0.cs b/src/Security/Authentication/JwtBearer/ref/Microsoft.AspNetCore.Authentication.JwtBearer.netcoreapp3.0.cs index 16d45e5809..8864d2ebad 100644 --- a/src/Security/Authentication/JwtBearer/ref/Microsoft.AspNetCore.Authentication.JwtBearer.netcoreapp3.0.cs +++ b/src/Security/Authentication/JwtBearer/ref/Microsoft.AspNetCore.Authentication.JwtBearer.netcoreapp3.0.cs @@ -8,6 +8,10 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public AuthenticationFailedContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions)) { } public System.Exception Exception { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } } + public partial class ForbiddenContext : Microsoft.AspNetCore.Authentication.ResultContext + { + public ForbiddenContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions options) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions)) { } + } public partial class JwtBearerChallengeContext : Microsoft.AspNetCore.Authentication.PropertiesContext { public JwtBearerChallengeContext(Microsoft.AspNetCore.Http.HttpContext context, Microsoft.AspNetCore.Authentication.AuthenticationScheme scheme, Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions options, Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) : base (default(Microsoft.AspNetCore.Http.HttpContext), default(Microsoft.AspNetCore.Authentication.AuthenticationScheme), default(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerOptions), default(Microsoft.AspNetCore.Authentication.AuthenticationProperties)) { } @@ -27,10 +31,12 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public JwtBearerEvents() { } public System.Func OnAuthenticationFailed { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Func OnChallenge { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } + public System.Func OnForbidden { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Func OnMessageReceived { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public System.Func OnTokenValidated { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public virtual System.Threading.Tasks.Task AuthenticationFailed(Microsoft.AspNetCore.Authentication.JwtBearer.AuthenticationFailedContext context) { throw null; } public virtual System.Threading.Tasks.Task Challenge(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerChallengeContext context) { throw null; } + public virtual System.Threading.Tasks.Task Forbidden(Microsoft.AspNetCore.Authentication.JwtBearer.ForbiddenContext context) { throw null; } public virtual System.Threading.Tasks.Task MessageReceived(Microsoft.AspNetCore.Authentication.JwtBearer.MessageReceivedContext context) { throw null; } public virtual System.Threading.Tasks.Task TokenValidated(Microsoft.AspNetCore.Authentication.JwtBearer.TokenValidatedContext context) { throw null; } } @@ -43,6 +49,7 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer protected override System.Threading.Tasks.Task HandleAuthenticateAsync() { throw null; } [System.Diagnostics.DebuggerStepThroughAttribute] protected override System.Threading.Tasks.Task HandleChallengeAsync(Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) { throw null; } + protected override System.Threading.Tasks.Task HandleForbiddenAsync(Microsoft.AspNetCore.Authentication.AuthenticationProperties properties) { throw null; } } public partial class JwtBearerOptions : Microsoft.AspNetCore.Authentication.AuthenticationSchemeOptions { diff --git a/src/Security/Authentication/JwtBearer/src/ForbiddenContext.cs b/src/Security/Authentication/JwtBearer/src/ForbiddenContext.cs new file mode 100644 index 0000000000..deb75ee378 --- /dev/null +++ b/src/Security/Authentication/JwtBearer/src/ForbiddenContext.cs @@ -0,0 +1,17 @@ +// 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.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Authentication.JwtBearer +{ + public class ForbiddenContext : ResultContext + { + public ForbiddenContext( + HttpContext context, + AuthenticationScheme scheme, + JwtBearerOptions options) + : base(context, scheme, options) { } + } +} diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerEvents.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerEvents.cs old mode 100644 new mode 100755 index a9b35c310f..c79e342740 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerEvents.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerEvents.cs @@ -16,6 +16,11 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer /// public Func OnAuthenticationFailed { get; set; } = context => Task.CompletedTask; + /// + /// Invoked if Authorization fails and results in a Forbidden response + /// + public Func OnForbidden { get; set; } = context => Task.CompletedTask; + /// /// Invoked when a protocol message is first received. /// @@ -33,6 +38,8 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer public virtual Task AuthenticationFailed(AuthenticationFailedContext context) => OnAuthenticationFailed(context); + public virtual Task Forbidden(ForbiddenContext context) => OnForbidden(context); + public virtual Task MessageReceived(MessageReceivedContext context) => OnMessageReceived(context); public virtual Task TokenValidated(TokenValidatedContext context) => OnTokenValidated(context); diff --git a/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs b/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs index 84ff83a765..a190613d9d 100644 --- a/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs +++ b/src/Security/Authentication/JwtBearer/src/JwtBearerHandler.cs @@ -265,6 +265,13 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer } } + protected override Task HandleForbiddenAsync(AuthenticationProperties properties) + { + var forbiddenContext = new ForbiddenContext(Context, Scheme, Options); + Response.StatusCode = 403; + return Events.Forbidden(forbiddenContext); + } + private static string CreateErrorDescription(Exception authFailure) { IEnumerable exceptions; diff --git a/src/Security/Authentication/test/JwtBearerTests.cs b/src/Security/Authentication/test/JwtBearerTests.cs old mode 100644 new mode 100755 index faa26d9f5d..0fe3e85cd7 --- a/src/Security/Authentication/test/JwtBearerTests.cs +++ b/src/Security/Authentication/test/JwtBearerTests.cs @@ -679,6 +679,77 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer Assert.Equal(string.Empty, response.ResponseText); } + [Fact] + public async Task EventOnForbidden_ResponseNotModified() + { + var tokenData = CreateStandardTokenAndKey(); + + var server = CreateServer(o => + { + o.TokenValidationParameters = new TokenValidationParameters() + { + ValidIssuer = "issuer.contoso.com", + ValidAudience = "audience.contoso.com", + IssuerSigningKey = tokenData.key, + }; + }); + var newBearerToken = "Bearer " + tokenData.tokenText; + var response = await SendAsync(server, "http://example.com/forbidden", newBearerToken); + Assert.Equal(HttpStatusCode.Forbidden, response.Response.StatusCode); + } + + [Fact] + public async Task EventOnForbiddenSkip_ResponseNotModified() + { + var tokenData = CreateStandardTokenAndKey(); + var server = CreateServer(o => + { + o.TokenValidationParameters = new TokenValidationParameters() + { + ValidIssuer = "issuer.contoso.com", + ValidAudience = "audience.contoso.com", + IssuerSigningKey = tokenData.key, + }; + o.Events = new JwtBearerEvents() + { + OnForbidden = context => + { + return Task.FromResult(0); + } + }; + }); + var newBearerToken = "Bearer " + tokenData.tokenText; + var response = await SendAsync(server, "http://example.com/forbidden", newBearerToken); + Assert.Equal(HttpStatusCode.Forbidden, response.Response.StatusCode); + } + + [Fact] + public async Task EventOnForbidden_ResponseModified() + { + var tokenData = CreateStandardTokenAndKey(); + var server = CreateServer(o => + { + o.TokenValidationParameters = new TokenValidationParameters() + { + ValidIssuer = "issuer.contoso.com", + ValidAudience = "audience.contoso.com", + IssuerSigningKey = tokenData.key, + }; + o.Events = new JwtBearerEvents() + { + OnForbidden = context => + { + context.Response.StatusCode = 418; + return context.Response.WriteAsync("You Shall Not Pass"); + } + }; + }); + var newBearerToken = "Bearer " + tokenData.tokenText; + var response = await SendAsync(server, "http://example.com/forbidden", newBearerToken); + Assert.Equal(418, (int)response.Response.StatusCode); + Assert.Equal("You Shall Not Pass", await response.Response.Content.ReadAsStringAsync()); + } + class InvalidTokenValidator : ISecurityTokenValidator { public InvalidTokenValidator() @@ -879,6 +950,11 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); await context.ChallengeAsync(JwtBearerDefaults.AuthenticationScheme); } + else if (context.Request.Path == new PathString("/forbidden")) + { + // Simulate Forbidden + await context.ForbidAsync(JwtBearerDefaults.AuthenticationScheme); + } else if (context.Request.Path == new PathString("/signIn")) { await Assert.ThrowsAsync(() => context.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal())); @@ -924,5 +1000,26 @@ namespace Microsoft.AspNetCore.Authentication.JwtBearer return transaction; } + + private static (string tokenText, SymmetricSecurityKey key) CreateStandardTokenAndKey() + { + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(new string('a', 128))); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var claims = new[] + { + new Claim(ClaimTypes.NameIdentifier, "Bob") + }; + + var token = new JwtSecurityToken( + issuer: "issuer.contoso.com", + audience: "audience.contoso.com", + claims: claims, + expires: DateTime.Now.AddMinutes(30), + signingCredentials: creds); + + var tokenText = new JwtSecurityTokenHandler().WriteToken(token); + return (tokenText, key); + } } }