Merge pull request #2259 from DuendeSoftware/pg/without-context

Avoid relying on an active Http Context
This commit is contained in:
Pieter Germishuys 2025-11-11 07:48:54 +01:00 committed by GitHub
commit ba91078275
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 96 additions and 5 deletions

View file

@ -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<AuthenticationOptions> options,
IOptions<BffOptions> 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;

View file

@ -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<string>();
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<BackgroundWorker>();
};
await InitializeAsync();
workerStarted.SetResult();
var content = await contentReceived.Task.WaitAsync(TimeSpan.FromSeconds(5));
content.ShouldNotBeNullOrEmpty();
}
internal class BackgroundWorker(
IHttpClientFactory httpClientFactory,
TaskCompletionSource<string> 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);
}
}
}
}