Merge pull request #2363 from DuendeSoftware/ev/bff/fix-no-forbid-scheme

Fix stack overflow exception when explicitly configuring authentication schemes without configuring a forbid scheme.
This commit is contained in:
Erwin van der Valk 2026-02-17 17:08:14 +01:00
parent 66544530c6
commit 11319ecd32
2 changed files with 65 additions and 6 deletions

View file

@ -18,13 +18,16 @@ internal class BffConfigureAuthenticationOptions : IPostConfigureOptions<Authent
options.DefaultScheme = BffAuthenticationSchemes.BffCookie;
options.DefaultChallengeScheme = BffAuthenticationSchemes.BffOpenIdConnect;
options.DefaultSignOutScheme = BffAuthenticationSchemes.BffOpenIdConnect;
}
// If we don't set this forbid scheme, when calling forbid, it can trigger a stackoverflow exception
// when calling HttpContext.Forbid().
if (options.DefaultForbidScheme == null)
{
options.DefaultForbidScheme = BffAuthenticationSchemes.BffCookie;
}
// If we don't set this forbid scheme, when calling forbid, it can trigger a stackoverflow exception
// when calling HttpContext.Forbid(). This happens because BffAuthenticationService decorates
// IAuthenticationService, and if the default forbid scheme is not set, the cookie handler's base
// class calls Context.ForbidAsync() which resolves BffAuthenticationService again, creating an
// infinite loop. We set the forbid scheme to the default scheme to break this cycle.
if (options.DefaultForbidScheme == null)
{
options.DefaultForbidScheme = options.DefaultScheme ?? options.DefaultAuthenticateScheme;
}
}
}

View file

@ -2,6 +2,7 @@
// See LICENSE in the project root for license information.
using System.Net;
using Duende.Bff.DynamicFrontends;
using Duende.Bff.Tests.TestFramework;
using Duende.Bff.Tests.TestInfra;
using Microsoft.AspNetCore.Authentication;
@ -311,4 +312,59 @@ public class LocalEndpointTests(ITestOutputHelper output) : BffTestBase(output)
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/not-found"));
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
}
[Fact]
public async Task authorization_policy_failure_should_return_403()
{
var identityServer = new IdentityServerTestHost(Context);
var bff = new BffTestHost(Context, identityServer);
identityServer.AddClient(The.ClientId, bff.Url());
bff.OnConfigureBffOptions += opt =>
{
opt.BackchannelHttpHandler = Internet;
opt.ConfigureOpenIdConnectDefaults = The.DefaultOpenIdConnectConfiguration;
};
// this is regards to an issue reported by one of our users:
// https://github.com/orgs/DuendeSoftware/discussions/488
// We have to explicitly configure the authentication schemes, but
// not set the DefaultForbidScheme, otherwise when an authorization policy
// fails, the BFF doesn't know which scheme to use for the forbid response,
// and that causes a StackOverflowException.
bff.OnConfigureServices += s => s.AddAuthentication(options =>
{
options.DefaultScheme = BffAuthenticationSchemes.BffCookie;
options.DefaultChallengeScheme = BffAuthenticationSchemes.BffOpenIdConnect;
options.DefaultSignOutScheme = BffAuthenticationSchemes.BffOpenIdConnect;
});
// This test verifies that when an authorization policy fails (not just RequireAuthenticatedUser,
// but a custom policy like RequireClaim), the BFF correctly returns 403 without causing
// a StackOverflowException. This was a bug when DefaultForbidScheme was not set.
AddCustomUserClaims(new System.Security.Claims.Claim("given_name", "Alice"));
bff.OnConfigureApp += app =>
{
app.Map(The.Path, c => ApiHost.ReturnApiCallDetails(c, () => LocalApiResponseStatus))
.RequireAuthorization(policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("given_name", "Bob"); // Alice won't have this claim
})
.AsBffApiEndpoint();
};
await identityServer.InitializeAsync();
await bff.InitializeAsync();
await bff.BrowserClient.Login();
bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
await bff.BrowserClient.CallBffHostApi(
url: Bff.Url(The.Path),
expectedStatusCode: HttpStatusCode.Forbidden
);
}
}