Skip to content

Commit 7d896e6

Browse files
Merge pull request #2364 from DuendeSoftware/cherry-pick/bff/fix-no-forbid-scheme-4.1.x
Cherry-pick: fix no forbid scheme (#2363)
2 parents 6654453 + 11319ec commit 7d896e6

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

bff/src/Bff/DynamicFrontends/BffConfigureAuthenticationOptions.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ public void PostConfigure(string? name, AuthenticationOptions options)
1818
options.DefaultScheme = BffAuthenticationSchemes.BffCookie;
1919
options.DefaultChallengeScheme = BffAuthenticationSchemes.BffOpenIdConnect;
2020
options.DefaultSignOutScheme = BffAuthenticationSchemes.BffOpenIdConnect;
21+
}
2122

22-
// If we don't set this forbid scheme, when calling forbid, it can trigger a stackoverflow exception
23-
// when calling HttpContext.Forbid().
24-
if (options.DefaultForbidScheme == null)
25-
{
26-
options.DefaultForbidScheme = BffAuthenticationSchemes.BffCookie;
27-
}
23+
// If we don't set this forbid scheme, when calling forbid, it can trigger a stackoverflow exception
24+
// when calling HttpContext.Forbid(). This happens because BffAuthenticationService decorates
25+
// IAuthenticationService, and if the default forbid scheme is not set, the cookie handler's base
26+
// class calls Context.ForbidAsync() which resolves BffAuthenticationService again, creating an
27+
// infinite loop. We set the forbid scheme to the default scheme to break this cycle.
28+
if (options.DefaultForbidScheme == null)
29+
{
30+
options.DefaultForbidScheme = options.DefaultScheme ?? options.DefaultAuthenticateScheme;
2831
}
2932
}
3033
}

bff/test/Bff.Tests/Endpoints/LocalEndpointTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// See LICENSE in the project root for license information.
33

44
using System.Net;
5+
using Duende.Bff.DynamicFrontends;
56
using Duende.Bff.Tests.TestFramework;
67
using Duende.Bff.Tests.TestInfra;
78
using Microsoft.AspNetCore.Authentication;
@@ -311,4 +312,59 @@ public async Task fallback_policy_should_not_fail(BffSetupType setup)
311312
var response = await Bff.BrowserClient.GetAsync(Bff.Url("/not-found"));
312313
response.StatusCode.ShouldBe(HttpStatusCode.NotFound);
313314
}
315+
316+
[Fact]
317+
318+
public async Task authorization_policy_failure_should_return_403()
319+
{
320+
var identityServer = new IdentityServerTestHost(Context);
321+
var bff = new BffTestHost(Context, identityServer);
322+
identityServer.AddClient(The.ClientId, bff.Url());
323+
324+
bff.OnConfigureBffOptions += opt =>
325+
{
326+
opt.BackchannelHttpHandler = Internet;
327+
opt.ConfigureOpenIdConnectDefaults = The.DefaultOpenIdConnectConfiguration;
328+
};
329+
330+
// this is regards to an issue reported by one of our users:
331+
// https://github.com/orgs/DuendeSoftware/discussions/488
332+
// We have to explicitly configure the authentication schemes, but
333+
// not set the DefaultForbidScheme, otherwise when an authorization policy
334+
// fails, the BFF doesn't know which scheme to use for the forbid response,
335+
// and that causes a StackOverflowException.
336+
bff.OnConfigureServices += s => s.AddAuthentication(options =>
337+
{
338+
options.DefaultScheme = BffAuthenticationSchemes.BffCookie;
339+
options.DefaultChallengeScheme = BffAuthenticationSchemes.BffOpenIdConnect;
340+
options.DefaultSignOutScheme = BffAuthenticationSchemes.BffOpenIdConnect;
341+
});
342+
343+
// This test verifies that when an authorization policy fails (not just RequireAuthenticatedUser,
344+
// but a custom policy like RequireClaim), the BFF correctly returns 403 without causing
345+
// a StackOverflowException. This was a bug when DefaultForbidScheme was not set.
346+
AddCustomUserClaims(new System.Security.Claims.Claim("given_name", "Alice"));
347+
348+
bff.OnConfigureApp += app =>
349+
{
350+
app.Map(The.Path, c => ApiHost.ReturnApiCallDetails(c, () => LocalApiResponseStatus))
351+
.RequireAuthorization(policy =>
352+
{
353+
policy.RequireAuthenticatedUser();
354+
policy.RequireClaim("given_name", "Bob"); // Alice won't have this claim
355+
})
356+
.AsBffApiEndpoint();
357+
};
358+
359+
await identityServer.InitializeAsync();
360+
await bff.InitializeAsync();
361+
362+
await bff.BrowserClient.Login();
363+
bff.BrowserClient.RedirectHandler.AutoFollowRedirects = false;
364+
365+
await bff.BrowserClient.CallBffHostApi(
366+
url: Bff.Url(The.Path),
367+
expectedStatusCode: HttpStatusCode.Forbidden
368+
);
369+
}
314370
}

0 commit comments

Comments
 (0)