// 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 System.Linq; using System.Net; using System.Net.Http; using System.Reflection; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using System.Xml.Linq; using Microsoft.AspNetCore.Authentication.Tests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.TestHost; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; using Xunit; namespace Microsoft.AspNetCore.Authentication.JwtBearer { public class JwtBearerTests { private void ConfigureDefaults(JwtBearerOptions o) { } [Fact] public async Task CanForwardDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.AddScheme("auth1", "auth1"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; }); var forwardDefault = new TestHandler(); services.AddSingleton(forwardDefault); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; Assert.Equal(0, forwardDefault.AuthenticateCount); Assert.Equal(0, forwardDefault.ForbidCount); Assert.Equal(0, forwardDefault.ChallengeCount); Assert.Equal(0, forwardDefault.SignInCount); Assert.Equal(0, forwardDefault.SignOutCount); await context.AuthenticateAsync(); Assert.Equal(1, forwardDefault.AuthenticateCount); await context.ForbidAsync(); Assert.Equal(1, forwardDefault.ForbidCount); await context.ChallengeAsync(); Assert.Equal(1, forwardDefault.ChallengeCount); await Assert.ThrowsAsync(() => context.SignOutAsync()); await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); } [Fact] public async Task ForwardSignInThrows() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.AddScheme("auth1", "auth1"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardSignOut = "specific"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); } [Fact] public async Task ForwardSignOutThrows() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.AddScheme("auth1", "auth1"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardSignOut = "specific"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await Assert.ThrowsAsync(() => context.SignOutAsync()); } [Fact] public async Task ForwardForbidWinsOverDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultSignInScheme = "auth1"; o.AddScheme("auth1", "auth1"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardForbid = "specific"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await context.ForbidAsync(); Assert.Equal(0, specific.SignOutCount); Assert.Equal(0, specific.AuthenticateCount); Assert.Equal(1, specific.ForbidCount); Assert.Equal(0, specific.ChallengeCount); Assert.Equal(0, specific.SignInCount); Assert.Equal(0, forwardDefault.AuthenticateCount); Assert.Equal(0, forwardDefault.ForbidCount); Assert.Equal(0, forwardDefault.ChallengeCount); Assert.Equal(0, forwardDefault.SignInCount); Assert.Equal(0, forwardDefault.SignOutCount); } [Fact] public async Task ForwardAuthenticateWinsOverDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultSignInScheme = "auth1"; o.AddScheme("auth1", "auth1"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardAuthenticate = "specific"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await context.AuthenticateAsync(); Assert.Equal(0, specific.SignOutCount); Assert.Equal(1, specific.AuthenticateCount); Assert.Equal(0, specific.ForbidCount); Assert.Equal(0, specific.ChallengeCount); Assert.Equal(0, specific.SignInCount); Assert.Equal(0, forwardDefault.AuthenticateCount); Assert.Equal(0, forwardDefault.ForbidCount); Assert.Equal(0, forwardDefault.ChallengeCount); Assert.Equal(0, forwardDefault.SignInCount); Assert.Equal(0, forwardDefault.SignOutCount); } [Fact] public async Task ForwardChallengeWinsOverDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.DefaultSignInScheme = "auth1"; o.AddScheme("specific", "specific"); o.AddScheme("auth1", "auth1"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardChallenge = "specific"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await context.ChallengeAsync(); Assert.Equal(0, specific.SignOutCount); Assert.Equal(0, specific.AuthenticateCount); Assert.Equal(0, specific.ForbidCount); Assert.Equal(1, specific.ChallengeCount); Assert.Equal(0, specific.SignInCount); Assert.Equal(0, forwardDefault.AuthenticateCount); Assert.Equal(0, forwardDefault.ForbidCount); Assert.Equal(0, forwardDefault.ChallengeCount); Assert.Equal(0, forwardDefault.SignInCount); Assert.Equal(0, forwardDefault.SignOutCount); } [Fact] public async Task ForwardSelectorWinsOverDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.AddScheme("auth1", "auth1"); o.AddScheme("selector", "selector"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardDefaultSelector = _ => "selector"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var selector = new TestHandler3(); services.AddSingleton(selector); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await context.AuthenticateAsync(); Assert.Equal(1, selector.AuthenticateCount); await context.ForbidAsync(); Assert.Equal(1, selector.ForbidCount); await context.ChallengeAsync(); Assert.Equal(1, selector.ChallengeCount); await Assert.ThrowsAsync(() => context.SignOutAsync()); await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); Assert.Equal(0, forwardDefault.AuthenticateCount); Assert.Equal(0, forwardDefault.ForbidCount); Assert.Equal(0, forwardDefault.ChallengeCount); Assert.Equal(0, forwardDefault.SignInCount); Assert.Equal(0, forwardDefault.SignOutCount); Assert.Equal(0, specific.AuthenticateCount); Assert.Equal(0, specific.ForbidCount); Assert.Equal(0, specific.ChallengeCount); Assert.Equal(0, specific.SignInCount); Assert.Equal(0, specific.SignOutCount); } [Fact] public async Task NullForwardSelectorUsesDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.AddScheme("auth1", "auth1"); o.AddScheme("selector", "selector"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardDefaultSelector = _ => null; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var selector = new TestHandler3(); services.AddSingleton(selector); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await context.AuthenticateAsync(); Assert.Equal(1, forwardDefault.AuthenticateCount); await context.ForbidAsync(); Assert.Equal(1, forwardDefault.ForbidCount); await context.ChallengeAsync(); Assert.Equal(1, forwardDefault.ChallengeCount); await Assert.ThrowsAsync(() => context.SignOutAsync()); await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); Assert.Equal(0, selector.AuthenticateCount); Assert.Equal(0, selector.ForbidCount); Assert.Equal(0, selector.ChallengeCount); Assert.Equal(0, selector.SignInCount); Assert.Equal(0, selector.SignOutCount); Assert.Equal(0, specific.AuthenticateCount); Assert.Equal(0, specific.ForbidCount); Assert.Equal(0, specific.ChallengeCount); Assert.Equal(0, specific.SignInCount); Assert.Equal(0, specific.SignOutCount); } [Fact] public async Task SpecificForwardWinsOverSelectorAndDefault() { var services = new ServiceCollection().AddLogging(); services.AddAuthentication(o => { o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; o.AddScheme("auth1", "auth1"); o.AddScheme("selector", "selector"); o.AddScheme("specific", "specific"); }) .AddJwtBearer(o => { ConfigureDefaults(o); o.ForwardDefault = "auth1"; o.ForwardDefaultSelector = _ => "selector"; o.ForwardAuthenticate = "specific"; o.ForwardChallenge = "specific"; o.ForwardSignIn = "specific"; o.ForwardSignOut = "specific"; o.ForwardForbid = "specific"; }); var specific = new TestHandler(); services.AddSingleton(specific); var forwardDefault = new TestHandler2(); services.AddSingleton(forwardDefault); var selector = new TestHandler3(); services.AddSingleton(selector); var sp = services.BuildServiceProvider(); var context = new DefaultHttpContext(); context.RequestServices = sp; await context.AuthenticateAsync(); Assert.Equal(1, specific.AuthenticateCount); await context.ForbidAsync(); Assert.Equal(1, specific.ForbidCount); await context.ChallengeAsync(); Assert.Equal(1, specific.ChallengeCount); await Assert.ThrowsAsync(() => context.SignOutAsync()); await Assert.ThrowsAsync(() => context.SignInAsync(new ClaimsPrincipal())); Assert.Equal(0, forwardDefault.AuthenticateCount); Assert.Equal(0, forwardDefault.ForbidCount); Assert.Equal(0, forwardDefault.ChallengeCount); Assert.Equal(0, forwardDefault.SignInCount); Assert.Equal(0, forwardDefault.SignOutCount); Assert.Equal(0, selector.AuthenticateCount); Assert.Equal(0, selector.ForbidCount); Assert.Equal(0, selector.ChallengeCount); Assert.Equal(0, selector.SignInCount); Assert.Equal(0, selector.SignOutCount); } [Fact] public async Task VerifySchemeDefaults() { var services = new ServiceCollection(); services.AddAuthentication().AddJwtBearer(); var sp = services.BuildServiceProvider(); var schemeProvider = sp.GetRequiredService(); var scheme = await schemeProvider.GetSchemeAsync(JwtBearerDefaults.AuthenticationScheme); Assert.NotNull(scheme); Assert.Equal("JwtBearerHandler", scheme.HandlerType.Name); Assert.Null(scheme.DisplayName); } [ConditionalFact(Skip = "Need to remove dependency on AAD since the generated tokens will expire")] [FrameworkSkipCondition(RuntimeFrameworks.Mono)] // https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/179 public async Task BearerTokenValidation() { var server = CreateServer(o => { o.Authority = "https://login.windows.net/tushartest.onmicrosoft.com"; o.Audience = "https://TusharTest.onmicrosoft.com/TodoListService-ManualJwt"; o.TokenValidationParameters.ValidateLifetime = false; }); var newBearerToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImtyaU1QZG1Cdng2OHNrVDgtbVBBQjNCc2VlQSJ9.eyJhdWQiOiJodHRwczovL1R1c2hhclRlc3Qub25taWNyb3NvZnQuY29tL1RvZG9MaXN0U2VydmljZS1NYW51YWxKd3QiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9hZmJlY2UwMy1hZWFhLTRmM2YtODVlNy1jZTA4ZGQyMGNlNTAvIiwiaWF0IjoxNDE4MzMwNjE0LCJuYmYiOjE0MTgzMzA2MTQsImV4cCI6MTQxODMzNDUxNCwidmVyIjoiMS4wIiwidGlkIjoiYWZiZWNlMDMtYWVhYS00ZjNmLTg1ZTctY2UwOGRkMjBjZTUwIiwiYW1yIjpbInB3ZCJdLCJvaWQiOiI1Mzk3OTdjMi00MDE5LTQ2NTktOWRiNS03MmM0Yzc3NzhhMzMiLCJ1cG4iOiJWaWN0b3JAVHVzaGFyVGVzdC5vbm1pY3Jvc29mdC5jb20iLCJ1bmlxdWVfbmFtZSI6IlZpY3RvckBUdXNoYXJUZXN0Lm9ubWljcm9zb2Z0LmNvbSIsInN1YiI6IkQyMm9aMW9VTzEzTUFiQXZrdnFyd2REVE80WXZJdjlzMV9GNWlVOVUwYnciLCJmYW1pbHlfbmFtZSI6Ikd1cHRhIiwiZ2l2ZW5fbmFtZSI6IlZpY3RvciIsImFwcGlkIjoiNjEzYjVhZjgtZjJjMy00MWI2LWExZGMtNDE2Yzk3ODAzMGI3IiwiYXBwaWRhY3IiOiIwIiwic2NwIjoidXNlcl9pbXBlcnNvbmF0aW9uIiwiYWNyIjoiMSJ9.N_Kw1EhoVGrHbE6hOcm7ERdZ7paBQiNdObvp2c6T6n5CE8p0fZqmUd-ya_EqwElcD6SiKSiP7gj0gpNUnOJcBl_H2X8GseaeeMxBrZdsnDL8qecc6_ygHruwlPltnLTdka67s1Ow4fDSHaqhVTEk6lzGmNEcbNAyb0CxQxU6o7Fh0yHRiWoLsT8yqYk8nKzsHXfZBNby4aRo3_hXaa4i0SZLYfDGGYPdttG4vT_u54QGGd4Wzbonv2gjDlllOVGOwoJS6kfl1h8mk0qxdiIaT_ChbDWgkWvTB7bTvBE-EgHgV0XmAo0WtJeSxgjsG3KhhEPsONmqrSjhIUV4IVnF2w"; var response = await SendAsync(server, "http://example.com/oauth", newBearerToken); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); } [Fact] public async Task SignInThrows() { var server = CreateServer(); var transaction = await server.SendAsync("https://example.com/signIn"); Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); } [Fact] public async Task SignOutThrows() { var server = CreateServer(); var transaction = await server.SendAsync("https://example.com/signOut"); Assert.Equal(HttpStatusCode.OK, transaction.Response.StatusCode); } [Fact] public async Task ThrowAtAuthenticationFailedEvent() { var server = CreateServer(o => { o.Events = new JwtBearerEvents { OnAuthenticationFailed = context => { context.Response.StatusCode = 401; throw new Exception(); }, OnMessageReceived = context => { context.Token = "something"; return Task.FromResult(0); } }; o.SecurityTokenValidators.Clear(); o.SecurityTokenValidators.Insert(0, new InvalidTokenValidator()); }, 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() { var server = CreateServer(o => { o.Events = new JwtBearerEvents() { OnMessageReceived = context => { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique"), new Claim(ClaimTypes.Email, "bob@contoso.com"), new Claim(ClaimsIdentity.DefaultNameClaimType, "bob") }; context.Principal = new ClaimsPrincipal(new ClaimsIdentity(claims, context.Scheme.Name)); context.Success(); return Task.FromResult(null); } }; }); var response = await SendAsync(server, "http://example.com/oauth", "someHeader someblob"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Equal("Bob le Magnifique", response.ResponseText); } [Fact] public async Task NoHeaderReceived() { var server = CreateServer(); var response = await SendAsync(server, "http://example.com/oauth"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); } [Fact] public async Task HeaderWithoutBearerReceived() { var server = CreateServer(); var response = await SendAsync(server, "http://example.com/oauth", "Token"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); } [Fact] public async Task UnrecognizedTokenReceived() { var server = CreateServer(); 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.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("Bearer error=\"invalid_token\"", response.Response.Headers.WwwAuthenticate.First().ToString()); Assert.Equal("", response.ResponseText); } [Theory] [InlineData(typeof(SecurityTokenInvalidAudienceException), "The audience is invalid")] [InlineData(typeof(SecurityTokenInvalidIssuerException), "The issuer is invalid")] [InlineData(typeof(SecurityTokenNoExpirationException), "The token has no expiration")] [InlineData(typeof(SecurityTokenInvalidLifetimeException), "The token lifetime is invalid")] [InlineData(typeof(SecurityTokenNotYetValidException), "The token is not valid yet")] [InlineData(typeof(SecurityTokenExpiredException), "The token is expired")] [InlineData(typeof(SecurityTokenInvalidSignatureException), "The signature is invalid")] [InlineData(typeof(SecurityTokenSignatureKeyNotFoundException), "The signature key was not found")] public async Task ExceptionReportedInHeaderForAuthenticationFailures(Type errorType, string message) { var server = CreateServer(options => { options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new InvalidTokenValidator(errorType)); }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); Assert.Equal($"Bearer error=\"invalid_token\", error_description=\"{message}\"", response.Response.Headers.WwwAuthenticate.First().ToString()); Assert.Equal("", response.ResponseText); } [Theory] [InlineData(typeof(ArgumentException))] public async Task ExceptionNotReportedInHeaderForOtherFailures(Type errorType) { var server = CreateServer(options => { options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new InvalidTokenValidator(errorType)); }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); Assert.Equal("Bearer error=\"invalid_token\"", response.Response.Headers.WwwAuthenticate.First().ToString()); Assert.Equal("", response.ResponseText); } [Fact] public async Task ExceptionsReportedInHeaderForMultipleAuthenticationFailures() { var server = CreateServer(options => { options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new InvalidTokenValidator(typeof(SecurityTokenInvalidAudienceException))); options.SecurityTokenValidators.Add(new InvalidTokenValidator(typeof(SecurityTokenSignatureKeyNotFoundException))); }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); Assert.Equal("Bearer error=\"invalid_token\", error_description=\"The audience is invalid; The signature key was not found\"", response.Response.Headers.WwwAuthenticate.First().ToString()); Assert.Equal("", response.ResponseText); } [Theory] [InlineData("custom_error", "custom_description", "custom_uri")] [InlineData("custom_error", "custom_description", null)] [InlineData("custom_error", null, null)] [InlineData(null, "custom_description", "custom_uri")] [InlineData(null, "custom_description", null)] [InlineData(null, null, "custom_uri")] public async Task ExceptionsReportedInHeaderExposesUserDefinedError(string error, string description, string uri) { var server = CreateServer(options => { options.Events = new JwtBearerEvents { OnChallenge = context => { context.Error = error; context.ErrorDescription = description; context.ErrorUri = uri; return Task.FromResult(0); } }; }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); Assert.Equal("", response.ResponseText); var builder = new StringBuilder(JwtBearerDefaults.AuthenticationScheme); if (!string.IsNullOrEmpty(error)) { builder.Append(" error=\""); builder.Append(error); builder.Append("\""); } if (!string.IsNullOrEmpty(description)) { if (!string.IsNullOrEmpty(error)) { builder.Append(","); } builder.Append(" error_description=\""); builder.Append(description); builder.Append('\"'); } if (!string.IsNullOrEmpty(uri)) { if (!string.IsNullOrEmpty(error) || !string.IsNullOrEmpty(description)) { builder.Append(","); } builder.Append(" error_uri=\""); builder.Append(uri); builder.Append('\"'); } Assert.Equal(builder.ToString(), response.Response.Headers.WwwAuthenticate.First().ToString()); } [Fact] public async Task ExceptionNotReportedInHeaderWhenIncludeErrorDetailsIsFalse() { var server = CreateServer(o => { o.IncludeErrorDetails = false; }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); Assert.Equal("Bearer", response.Response.Headers.WwwAuthenticate.First().ToString()); Assert.Equal("", response.ResponseText); } [Fact] public async Task ExceptionNotReportedInHeaderWhenTokenWasMissing() { var server = CreateServer(); var response = await SendAsync(server, "http://example.com/oauth"); Assert.Equal(HttpStatusCode.Unauthorized, response.Response.StatusCode); Assert.Equal("Bearer", response.Response.Headers.WwwAuthenticate.First().ToString()); Assert.Equal("", response.ResponseText); } [Fact] public async Task CustomTokenValidated() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnTokenValidated = context => { // Retrieve the NameIdentifier claim from the identity // returned by the custom security token validator. var identity = (ClaimsIdentity)context.Principal.Identity; var identifier = identity.FindFirst(ClaimTypes.NameIdentifier); Assert.Equal("Bob le Tout Puissant", identifier.Value); // Remove the existing NameIdentifier claim and replace it // with a new one containing a different value. identity.RemoveClaim(identifier); // Make sure to use a different name identifier // than the one defined by BlobTokenValidator. identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, "Bob le Magnifique")); return Task.FromResult(null); } }; options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator(JwtBearerDefaults.AuthenticationScheme)); }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer someblob"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Equal("Bob le Magnifique", response.ResponseText); } [Fact] public async Task RetrievingTokenFromAlternateLocation() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnMessageReceived = context => { context.Token = "CustomToken"; return Task.FromResult(null); } }; options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT", token => { Assert.Equal("CustomToken", token); })); }); var response = await SendAsync(server, "http://example.com/oauth", "Bearer Token"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Equal("Bob le Tout Puissant", response.ResponseText); } [Fact] public async Task EventOnMessageReceivedSkip_NoMoreEventsExecuted() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnMessageReceived = context => { context.NoResult(); return Task.FromResult(0); }, OnTokenValidated = context => { throw new NotImplementedException(); }, OnAuthenticationFailed = context => { throw new NotImplementedException(context.Exception.ToString()); }, OnChallenge = context => { throw new NotImplementedException(); }, }; }); var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Equal(string.Empty, response.ResponseText); } [Fact] public async Task EventOnMessageReceivedReject_NoMoreEventsExecuted() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnMessageReceived = context => { context.Fail("Authentication was aborted from user code."); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, OnTokenValidated = context => { throw new NotImplementedException(); }, OnAuthenticationFailed = context => { throw new NotImplementedException(context.Exception.ToString()); }, OnChallenge = context => { throw new NotImplementedException(); }, }; }); var exception = await Assert.ThrowsAsync(delegate { return SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); }); Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message); } [Fact] public async Task EventOnTokenValidatedSkip_NoMoreEventsExecuted() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnTokenValidated = context => { context.NoResult(); return Task.FromResult(0); }, OnAuthenticationFailed = context => { throw new NotImplementedException(context.Exception.ToString()); }, OnChallenge = context => { throw new NotImplementedException(); }, }; options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); }); var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Equal(string.Empty, response.ResponseText); } [Fact] public async Task EventOnTokenValidatedReject_NoMoreEventsExecuted() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnTokenValidated = context => { context.Fail("Authentication was aborted from user code."); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, OnAuthenticationFailed = context => { throw new NotImplementedException(context.Exception.ToString()); }, OnChallenge = context => { throw new NotImplementedException(); }, }; options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); }); var exception = await Assert.ThrowsAsync(delegate { return SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); }); Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message); } [Fact] public async Task EventOnAuthenticationFailedSkip_NoMoreEventsExecuted() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnTokenValidated = context => { throw new Exception("Test Exception"); }, OnAuthenticationFailed = context => { context.NoResult(); return Task.FromResult(0); }, OnChallenge = context => { throw new NotImplementedException(); }, }; options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); }); var response = await SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Equal(string.Empty, response.ResponseText); } [Fact] public async Task EventOnAuthenticationFailedReject_NoMoreEventsExecuted() { var server = CreateServer(options => { options.Events = new JwtBearerEvents() { OnTokenValidated = context => { throw new Exception("Test Exception"); }, OnAuthenticationFailed = context => { context.Fail("Authentication was aborted from user code."); context.Response.StatusCode = StatusCodes.Status202Accepted; return Task.FromResult(0); }, OnChallenge = context => { throw new NotImplementedException(); }, }; options.SecurityTokenValidators.Clear(); options.SecurityTokenValidators.Add(new BlobTokenValidator("JWT")); }); var exception = await Assert.ThrowsAsync(delegate { return SendAsync(server, "http://example.com/checkforerrors", "Bearer Token"); }); Assert.Equal("Authentication was aborted from user code.", exception.InnerException.Message); } [Fact] public async Task EventOnChallengeSkip_ResponseNotModified() { var server = CreateServer(o => { o.Events = new JwtBearerEvents() { OnChallenge = context => { context.HandleResponse(); return Task.FromResult(0); }, }; }); var response = await SendAsync(server, "http://example.com/unauthorized", "Bearer Token"); Assert.Equal(HttpStatusCode.OK, response.Response.StatusCode); Assert.Empty(response.Response.Headers.WwwAuthenticate); Assert.Equal(string.Empty, response.ResponseText); } class InvalidTokenValidator : ISecurityTokenValidator { public InvalidTokenValidator() { ExceptionType = typeof(SecurityTokenException); } public InvalidTokenValidator(Type exceptionType) { ExceptionType = exceptionType; } public Type ExceptionType { get; set; } 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) { var constructor = ExceptionType.GetTypeInfo().GetConstructor(new[] { typeof(string) }); var exception = (Exception)constructor.Invoke(new[] { ExceptionType.Name }); throw exception; } } class BlobTokenValidator : ISecurityTokenValidator { private Action _tokenValidator; public BlobTokenValidator(string authenticationScheme) { AuthenticationScheme = authenticationScheme; } public BlobTokenValidator(string authenticationScheme, Action tokenValidator) { AuthenticationScheme = authenticationScheme; _tokenValidator = tokenValidator; } public string AuthenticationScheme { get; } 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) { validatedToken = null; _tokenValidator?.Invoke(securityToken); var claims = new[] { // Make sure to use a different name identifier // than the one defined by CustomTokenValidated. new Claim(ClaimTypes.NameIdentifier, "Bob le Tout Puissant"), new Claim(ClaimTypes.Email, "bob@contoso.com"), new Claim(ClaimsIdentity.DefaultNameClaimType, "bob"), }; return new ClaimsPrincipal(new ClaimsIdentity(claims, AuthenticationScheme)); } } private static TestServer CreateServer(Action options = null, Func, Task> handlerBeforeAuth = null) { var builder = new WebHostBuilder() .Configure(app => { if (handlerBeforeAuth != null) { app.Use(handlerBeforeAuth); } app.UseAuthentication(); app.Use(async (context, next) => { if (context.Request.Path == new PathString("/checkforerrors")) { var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); // this used to be "Automatic" if (result.Failure != null) { throw new Exception("Failed to authenticate", result.Failure); } return; } else if (context.Request.Path == new PathString("/oauth")) { if (context.User == null || context.User.Identity == null || !context.User.Identity.IsAuthenticated) { context.Response.StatusCode = 401; // REVIEW: no more automatic challenge await context.ChallengeAsync(JwtBearerDefaults.AuthenticationScheme); return; } var identifier = context.User.FindFirst(ClaimTypes.NameIdentifier); if (identifier == null) { context.Response.StatusCode = 500; return; } await context.Response.WriteAsync(identifier.Value); } else if (context.Request.Path == new PathString("/unauthorized")) { // Simulate Authorization failure var result = await context.AuthenticateAsync(JwtBearerDefaults.AuthenticationScheme); await context.ChallengeAsync(JwtBearerDefaults.AuthenticationScheme); } else if (context.Request.Path == new PathString("/signIn")) { await Assert.ThrowsAsync(() => context.SignInAsync(JwtBearerDefaults.AuthenticationScheme, new ClaimsPrincipal())); } else if (context.Request.Path == new PathString("/signOut")) { await Assert.ThrowsAsync(() => context.SignOutAsync(JwtBearerDefaults.AuthenticationScheme)); } else { await next(); } }); }) .ConfigureServices(services => services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options)); return new TestServer(builder); } // TODO: see if we can share the TestExtensions SendAsync method (only diff is auth header) private static async Task SendAsync(TestServer server, string uri, string authorizationHeader = null) { var request = new HttpRequestMessage(HttpMethod.Get, uri); if (!string.IsNullOrEmpty(authorizationHeader)) { request.Headers.Add("Authorization", authorizationHeader); } var transaction = new Transaction { Request = request, Response = await server.CreateClient().SendAsync(request), }; transaction.ResponseText = await transaction.Response.Content.ReadAsStringAsync(); if (transaction.Response.Content != null && transaction.Response.Content.Headers.ContentType != null && transaction.Response.Content.Headers.ContentType.MediaType == "text/xml") { transaction.ResponseElement = XElement.Parse(transaction.ResponseText); } return transaction; } } }