diff --git a/EXAMPLES.md b/EXAMPLES.md index b67fd76b..a6e796c8 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -335,6 +335,8 @@ Additionally, you will also need to call `UseBackchannelLogout();` on the Applic app.UseBackchannelLogout(); ``` +When using a custom scheme, make sure to use the same scheme name consistently throughout your authentication flow (login, logout, and backchannel logout configuration). + As logout tokens need to be stored, you will also need to provide something for our SDK to store the tokens in. ```csharp diff --git a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs index 945ac058..b30ad361 100644 --- a/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs +++ b/src/Auth0.AspNetCore.Authentication/Auth0WebAppAuthenticationBuilder.cs @@ -56,7 +56,10 @@ public Auth0WebAppAuthenticationBuilder WithAccessToken(ActionAn instance of public Auth0WebAppAuthenticationBuilder WithBackchannelLogout() { - _services.AddTransient(); + _services.AddTransient(sp => + new BackchannelLogoutHandler( + sp.GetRequiredService(), + _authenticationScheme)); _services.AddTransient(); return this; } diff --git a/src/Auth0.AspNetCore.Authentication/BackchannelLogout/BackchannelLogoutHandler.cs b/src/Auth0.AspNetCore.Authentication/BackchannelLogout/BackchannelLogoutHandler.cs index 1ed62810..2368a97f 100644 --- a/src/Auth0.AspNetCore.Authentication/BackchannelLogout/BackchannelLogoutHandler.cs +++ b/src/Auth0.AspNetCore.Authentication/BackchannelLogout/BackchannelLogoutHandler.cs @@ -16,10 +16,17 @@ namespace Auth0.AspNetCore.Authentication.BackchannelLogout public class BackchannelLogoutHandler { private readonly ILogoutTokenHandler _tokenHandler; + private readonly string _authenticationScheme; - public BackchannelLogoutHandler(ILogoutTokenHandler tokenHandler) + public BackchannelLogoutHandler(ILogoutTokenHandler tokenHandler) + : this(tokenHandler, Auth0Constants.AuthenticationScheme) + { + } + + public BackchannelLogoutHandler(ILogoutTokenHandler tokenHandler, string authenticationScheme) { _tokenHandler = tokenHandler; + _authenticationScheme = authenticationScheme; } public async Task HandleRequestAsync(HttpContext context) @@ -36,10 +43,10 @@ public async Task HandleRequestAsync(HttpContext context) { var auth0Options = context.RequestServices .GetRequiredService>() - .Get(Auth0Constants.AuthenticationScheme); + .Get(_authenticationScheme); var oidcOptions = context.RequestServices .GetRequiredService>() - .Get(Auth0Constants.AuthenticationScheme); + .Get(_authenticationScheme); var principal = await ValidateLogoutToken(logoutToken, oidcOptions, context); diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0.AspNetCore.Authentication.IntegrationTests.csproj b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0.AspNetCore.Authentication.IntegrationTests.csproj index d3439bf5..27de4927 100644 --- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0.AspNetCore.Authentication.IntegrationTests.csproj +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Auth0.AspNetCore.Authentication.IntegrationTests.csproj @@ -24,7 +24,7 @@ - + diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/BackchannelLogoutTests.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/BackchannelLogoutTests.cs index f30df969..0d4229ea 100644 --- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/BackchannelLogoutTests.cs +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/BackchannelLogoutTests.cs @@ -553,4 +553,44 @@ public async Task Should_Not_Logout_When_Sid_Doesnt_Match() protectedResponse2.StatusCode.Should().Be(HttpStatusCode.OK); protectedResponse2.Headers.Location.Should().BeNull(); } + + [Fact] + public async Task Should_Support_Custom_Authentication_Scheme() + { + var configuration = TestConfiguration.GetConfiguration(); + var domain = configuration["Auth0:Domain"]; + var clientId = configuration["Auth0:ClientId"]; + var customScheme = "CustomScheme"; + + var mockHandler = new OidcMockBuilder() + .MockOpenIdConfig() + .MockJwks() + .MockToken(() => new JwtTokenBuilder(1) + .WithIssuer($"https://{domain}/") + .WithAudience(clientId) + .Build()) + .Build(); + + // Create a server with a custom authentication scheme + using var server = TestServerBuilder.CreateServerWithCustomScheme(customScheme, opt => + { + opt.Backchannel = new HttpClient(mockHandler.Object); + }, null, false, false, false, null, true); + using var client = server.CreateClient(); + + // Create a valid logout token + var logoutToken = new JwtTokenBuilder(1) + .WithIssuer($"https://{domain}/") + .WithAudience(clientId) + .WithClaim(JwtRegisteredClaimNames.Sid, "sid") + .WithClaim("events", "{ \"http://schemas.openid.net/event/backchannel-logout\": {} }" ) + .Build(); + + var formData = new Dictionary { { "logout_token", logoutToken } }; + using var req = new HttpRequestMessage(HttpMethod.Post, $"{TestServerBuilder.Host}/backchannel-logout"); + req.Content = new FormUrlEncodedContent(formData); + using var response = await client.SendAsync(req); + + response.StatusCode.Should().Be((HttpStatusCode)200); + } } \ No newline at end of file diff --git a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs index 701636fd..80291e6a 100644 --- a/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs +++ b/tests/Auth0.AspNetCore.Authentication.IntegrationTests/Infrastructure/TestServerBuilder.cs @@ -30,8 +30,9 @@ internal class TestServerBuilder /// /// Action used to provide custom configuration for the Auth0 middleware. /// Indicated whether or not the authenitcation should be mocked, useful because some tests require an authenticated user while others require no user to exist. + /// Optional custom authentication scheme to use. /// The created TestServer instance. - public static TestServer CreateServer(Action configureOptions = null, Action configureWithAccessTokensOptions = null, bool mockAuthentication = false, bool useServiceCollectionExtension = false, bool addExtraProvider = false, Action configureAdditionalOptions = null, bool enableBackchannelLogout = false) + public static TestServer CreateServer(Action configureOptions = null, Action configureWithAccessTokensOptions = null, bool mockAuthentication = false, bool useServiceCollectionExtension = false, bool addExtraProvider = false, Action configureAdditionalOptions = null, bool enableBackchannelLogout = false, string authenticationScheme = null) { var configuration = TestConfiguration.GetConfiguration(); var host = new HostBuilder() @@ -78,13 +79,21 @@ await res.WriteAsync(JsonSerializer.Serialize(new Auth0WebAppAuthenticationBuilder builder; if (useServiceCollectionExtension) { - builder = services.AddAuth0WebAppAuthentication(options => - { - options.Domain = configuration["Auth0:Domain"]; - options.ClientId = configuration["Auth0:ClientId"]; + builder = string.IsNullOrEmpty(authenticationScheme) + ? services.AddAuth0WebAppAuthentication(options => + { + options.Domain = configuration["Auth0:Domain"]; + options.ClientId = configuration["Auth0:ClientId"]; - if (configureOptions != null) configureOptions(options); - }); + if (configureOptions != null) configureOptions(options); + }) + : services.AddAuth0WebAppAuthentication(authenticationScheme, options => + { + options.Domain = configuration["Auth0:Domain"]; + options.ClientId = configuration["Auth0:ClientId"]; + + if (configureOptions != null) configureOptions(options); + }); } else { @@ -98,13 +107,21 @@ await res.WriteAsync(JsonSerializer.Serialize(new } }); - builder = authenticationBuilder.AddAuth0WebAppAuthentication(options => - { - options.Domain = configuration["Auth0:Domain"]; - options.ClientId = configuration["Auth0:ClientId"]; + builder = string.IsNullOrEmpty(authenticationScheme) + ? authenticationBuilder.AddAuth0WebAppAuthentication(options => + { + options.Domain = configuration["Auth0:Domain"]; + options.ClientId = configuration["Auth0:ClientId"]; - if (configureOptions != null) configureOptions(options); - }); + if (configureOptions != null) configureOptions(options); + }) + : authenticationBuilder.AddAuth0WebAppAuthentication(authenticationScheme, options => + { + options.Domain = configuration["Auth0:Domain"]; + options.ClientId = configuration["Auth0:ClientId"]; + + if (configureOptions != null) configureOptions(options); + }); if (addExtraProvider) { @@ -147,6 +164,27 @@ await res.WriteAsync(JsonSerializer.Serialize(new host.Start(); return host.GetTestServer(); } + + /// + /// Create an instance of the TestServer with a custom authentication scheme to use for Integration Tests. + /// This is used to reproduce the issue where BackchannelLogoutHandler doesn't respect custom authentication schemes. + /// + /// The custom authentication scheme to use. + /// Action used to provide custom configuration for the Auth0 middleware. + /// Indicated whether or not the authenitcation should be mocked. + /// The created TestServer instance. + public static TestServer CreateServerWithCustomScheme(string authenticationScheme, Action configureOptions = null, Action configureWithAccessTokensOptions = null, bool mockAuthentication = false, bool useServiceCollectionExtension = false, bool addExtraProvider = false, Action configureAdditionalOptions = null, bool enableBackchannelLogout = false) + { + return CreateServer( + configureOptions: configureOptions, + configureWithAccessTokensOptions: configureWithAccessTokensOptions, + mockAuthentication: mockAuthentication, + useServiceCollectionExtension: useServiceCollectionExtension, + addExtraProvider: addExtraProvider, + configureAdditionalOptions: configureAdditionalOptions, + enableBackchannelLogout: enableBackchannelLogout, + authenticationScheme: authenticationScheme); + } } }