Fix JWT validation by configuring custom signing key resolver

- Added IssuerSigningKeyResolver to fetch JWKS directly from internal Keycloak URL
- This bypasses the localhost:8080 URLs in Keycloak's discovery document
- Ensures JWT tokens are validated against correct signing keys
This commit is contained in:
WorkClub Automation
2026-03-20 11:01:56 +01:00
parent 87c315c6fd
commit 13f9e7be7f
+36 -4
View File
@@ -51,21 +51,53 @@ builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
options.RequireHttpsMetadata = false; options.RequireHttpsMetadata = false;
options.MapInboundClaims = false; options.MapInboundClaims = false;
// For Docker internal communication, use the direct Keycloak URL for metadata // For Docker internal communication, configure metadata and signing key resolution
// This bypasses the hostname mismatch in Keycloak's discovery endpoint // to bypass the hostname mismatch in Keycloak's discovery endpoint
var keycloakAuthority = builder.Configuration["Keycloak:Authority"]; var keycloakAuthority = builder.Configuration["Keycloak:Authority"];
var keycloakInternalUrl = "http://keycloak:8081";
if (keycloakAuthority?.Contains("keycloak:") == true) if (keycloakAuthority?.Contains("keycloak:") == true)
{ {
// Set metadata address to internal Keycloak URL
options.MetadataAddress = $"{keycloakAuthority}/.well-known/openid-configuration"; options.MetadataAddress = $"{keycloakAuthority}/.well-known/openid-configuration";
}
// Configure custom signing key resolver to fetch from internal Keycloak URL
// This overrides the URLs returned in the discovery document
var httpClient = new HttpClient();
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{ {
ValidateIssuer = false, // Disabled for local dev - external clients use localhost:8080, internal use keycloak:8080 ValidateIssuer = false,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
{
// Fetch JWKS from internal Keycloak URL
var jwksUrl = $"{keycloakInternalUrl}/realms/workclub/protocol/openid-connect/certs";
try
{
var response = httpClient.GetStringAsync(jwksUrl).GetAwaiter().GetResult();
var jwks = new Microsoft.IdentityModel.Tokens.JsonWebKeySet(response);
return jwks.Keys;
}
catch (Exception ex)
{
Console.WriteLine($"Failed to fetch JWKS from {jwksUrl}: {ex.Message}");
return Array.Empty<Microsoft.IdentityModel.Tokens.SecurityKey>();
}
}
};
}
else
{
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = true, ValidateAudience = true,
ValidateLifetime = true, ValidateLifetime = true,
ValidateIssuerSigningKey = true ValidateIssuerSigningKey = true
}; };
}
options.Events = new JwtBearerEvents options.Events = new JwtBearerEvents
{ {
OnAuthenticationFailed = context => OnAuthenticationFailed = context =>