feat(#13): S-12c-1 — behandel BFF auth + werkbak (ADR-0013) #85
@@ -120,10 +120,21 @@ internal static class BehandelAuth
|
||||
return;
|
||||
|
||||
var realmAccess = principal.FindFirst("realm_access")?.Value;
|
||||
if (string.IsNullOrEmpty(realmAccess))
|
||||
if (string.IsNullOrWhiteSpace(realmAccess))
|
||||
return;
|
||||
|
||||
var roles = JsonSerializer.Deserialize<RealmAccess>(realmAccess)?.Roles ?? [];
|
||||
// A malformed realm_access claim must not fail authentication (a throw here becomes a 401);
|
||||
// it simply yields no roles, so the authorization policy answers 403.
|
||||
string[] roles;
|
||||
try
|
||||
{
|
||||
roles = JsonSerializer.Deserialize<RealmAccess>(realmAccess)?.Roles ?? [];
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var role in roles)
|
||||
identity.AddClaim(new Claim(identity.RoleClaimType, role));
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Mvc.Testing;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.IdentityModel.Protocols;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
@@ -23,24 +24,19 @@ internal sealed class BffFactory : WebApplicationFactory<Program>
|
||||
public FakeDomainClient Domain { get; } = new();
|
||||
public FakeProjectionClient Projection { get; } = new();
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
private static void ValidateWithTestKey(IServiceCollection services, string scheme) =>
|
||||
services.Configure<JwtBearerOptions>(scheme, options =>
|
||||
{
|
||||
builder.UseSetting("Keycloak:Authority", "https://keycloak.invalid/realms/digid");
|
||||
builder.UseSetting("Downstream:Domain:BaseUrl", "http://domain.invalid/");
|
||||
builder.UseSetting("Downstream:Projection:BaseUrl", "http://projection.invalid/");
|
||||
|
||||
builder.ConfigureTestServices(services =>
|
||||
{
|
||||
services.AddSingleton<IDomainClient>(Domain);
|
||||
services.AddSingleton<IProjectionClient>(Projection);
|
||||
|
||||
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options =>
|
||||
{
|
||||
// Validate locally against the test key; never reach out for OIDC metadata.
|
||||
// Validate locally against the test key and NEVER reach out for OIDC metadata. A static
|
||||
// configuration manager guarantees this regardless of Configure/PostConfigure ordering —
|
||||
// clearing Authority alone left the medewerker scheme fetching metadata under CI timing
|
||||
// (2s hang → 401), because JwtBearer's PostConfigure could still build a ConfigurationManager.
|
||||
options.Authority = null;
|
||||
options.MetadataAddress = null!;
|
||||
options.RequireHttpsMetadata = false;
|
||||
options.Configuration = new OpenIdConnectConfiguration();
|
||||
options.ConfigurationManager =
|
||||
new StaticConfigurationManager<OpenIdConnectConfiguration>(new OpenIdConnectConfiguration());
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuer = false,
|
||||
@@ -51,6 +47,24 @@ internal sealed class BffFactory : WebApplicationFactory<Program>
|
||||
ClockSkew = TimeSpan.Zero,
|
||||
};
|
||||
});
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
builder.UseSetting("Keycloak:Authority", "https://keycloak.invalid/realms/digid");
|
||||
builder.UseSetting("Keycloak:MedewerkerAuthority", "https://keycloak.invalid/realms/medewerker");
|
||||
builder.UseSetting("Downstream:Domain:BaseUrl", "http://domain.invalid/");
|
||||
builder.UseSetting("Downstream:Projection:BaseUrl", "http://projection.invalid/");
|
||||
|
||||
builder.ConfigureTestServices(services =>
|
||||
{
|
||||
services.AddSingleton<IDomainClient>(Domain);
|
||||
services.AddSingleton<IProjectionClient>(Projection);
|
||||
|
||||
// Both realms validate locally against the test key (no live Keycloak). The medewerker
|
||||
// scheme keeps its OnTokenValidated role-lifting from Program.cs — only the validation
|
||||
// parameters are swapped here.
|
||||
ValidateWithTestKey(services, JwtBearerDefaults.AuthenticationScheme);
|
||||
ValidateWithTestKey(services, "medewerker");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user