diff --git a/bff/src/Bff/DynamicFrontends/Internal/BffAuthenticationSchemeProvider.cs b/bff/src/Bff/DynamicFrontends/Internal/BffAuthenticationSchemeProvider.cs index dfaa28176..352179151 100644 --- a/bff/src/Bff/DynamicFrontends/Internal/BffAuthenticationSchemeProvider.cs +++ b/bff/src/Bff/DynamicFrontends/Internal/BffAuthenticationSchemeProvider.cs @@ -6,11 +6,13 @@ using Duende.Bff.Configuration; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; namespace Duende.Bff.DynamicFrontends.Internal; internal class BffAuthenticationSchemeProvider( + IHttpContextAccessor httpContextAccessor, CurrentFrontendAccessor currentFrontendAccessor, IOptions options, IOptions bffOptions) : AuthenticationSchemeProvider(options) @@ -19,9 +21,13 @@ internal class BffAuthenticationSchemeProvider( { var defaultSchemes = await base.GetRequestHandlerSchemesAsync(); - if (!currentFrontendAccessor.TryGet(out _) && bffOptions.Value.ConfigureOpenIdConnectDefaults != null) + if (httpContextAccessor.HttpContext != null + && !currentFrontendAccessor.TryGet(out _) + && bffOptions.Value.ConfigureOpenIdConnectDefaults != null) { - defaultSchemes = defaultSchemes.Append(new BffAuthenticationScheme(BffAuthenticationSchemes.BffOpenIdConnect, "Default Duende Bff OpenIdConnect", typeof(OpenIdConnectHandler))); + defaultSchemes = defaultSchemes.Append(new BffAuthenticationScheme( + BffAuthenticationSchemes.BffOpenIdConnect, "Default Duende Bff OpenIdConnect", + typeof(OpenIdConnectHandler))); } return defaultSchemes; @@ -36,16 +42,22 @@ internal class BffAuthenticationSchemeProvider( private BffAuthenticationScheme? GetBffAuthenticationScheme(string name) { - currentFrontendAccessor.TryGet(out var frontend); + BffFrontend? frontend = null; + if (httpContextAccessor.HttpContext != null) + { + currentFrontendAccessor.TryGet(out frontend); + } if (name == frontend?.CookieSchemeName || name == BffAuthenticationSchemes.BffCookie) { - return new BffAuthenticationScheme(frontend?.CookieSchemeName ?? BffAuthenticationSchemes.BffCookie, "Duende Bff Cookie", typeof(CookieAuthenticationHandler)); + return new BffAuthenticationScheme(frontend?.CookieSchemeName ?? BffAuthenticationSchemes.BffCookie, + "Duende Bff Cookie", typeof(CookieAuthenticationHandler)); } if (name == frontend?.OidcSchemeName || name == BffAuthenticationSchemes.BffOpenIdConnect) { - return new BffAuthenticationScheme(frontend?.OidcSchemeName ?? BffAuthenticationSchemes.BffOpenIdConnect, "Duende Bff OpenIdConnect", typeof(OpenIdConnectHandler)); + return new BffAuthenticationScheme(frontend?.OidcSchemeName ?? BffAuthenticationSchemes.BffOpenIdConnect, + "Duende Bff OpenIdConnect", typeof(OpenIdConnectHandler)); } return null; diff --git a/bff/test/Bff.Tests/BffScenarioTests.cs b/bff/test/Bff.Tests/BffScenarioTests.cs new file mode 100644 index 000000000..8d92bb3f2 --- /dev/null +++ b/bff/test/Bff.Tests/BffScenarioTests.cs @@ -0,0 +1,79 @@ +// Copyright (c) Duende Software. All rights reserved. +// See LICENSE in the project root for license information. + +using Duende.AccessTokenManagement; +using Duende.Bff.Tests.TestInfra; +using Xunit.Abstractions; + +namespace Duende.Bff.Tests; + +public class BffScenarioTests(ITestOutputHelper output) : BffTestBase(output) +{ + [Fact] + public async Task When_using_bff_as_host_and_client_credentials_token_manager_with_no_http_context_still_works() + { + var workerClientId = "worker.client.id"; + IdentityServer.AddClient(workerClientId, Bff.Url()); + var contentReceived = new TaskCompletionSource(); + var workerStarted = new TaskCompletionSource(); + + Bff.OnConfigureServices += services => + { + services.AddClientCredentialsTokenManagement() + .AddClient(ClientCredentialsClientName.Parse("worker.client"), client => + { + client.TokenEndpoint = new Uri(IdentityServer.Url(), "/connect/token"); + client.ClientId = ClientId.Parse(workerClientId); + client.ClientSecret = ClientSecret.Parse(The.ClientSecret); + client.Scope = Scope.Parse(The.Scope); + client.HttpClient = new HttpClient(Internet, disposeHandler: false); + }); + + services.AddClientCredentialsHttpClient("worker", + ClientCredentialsClientName.Parse("worker.client"), + client => { client.BaseAddress = Api.Url(); }) + .ConfigurePrimaryHttpMessageHandler(() => Internet); + + services.AddSingleton(contentReceived); + services.AddSingleton(workerStarted); + services.AddHostedService(); + }; + await InitializeAsync(); + workerStarted.SetResult(); + var content = await contentReceived.Task.WaitAsync(TimeSpan.FromSeconds(5)); + content.ShouldNotBeNullOrEmpty(); + } + + internal class BackgroundWorker( + IHttpClientFactory httpClientFactory, + TaskCompletionSource contentReceived, + TaskCompletionSource workerIsAllowedToStart) : BackgroundService + { + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await workerIsAllowedToStart.Task; + + var client = httpClientFactory.CreateClient("worker"); + + try + { + var response = await client.GetAsync("/", stoppingToken); + + if (response.IsSuccessStatusCode) + { + var content = await response.Content.ReadAsStringAsync(stoppingToken); + contentReceived.TrySetResult(content); + } + else + { + contentReceived.TrySetException( + new Exception($"Request failed with status code: {response.StatusCode}")); + } + } + catch (Exception ex) + { + contentReceived.TrySetException(ex); + } + } + } +}