feat: generic OIDC role extraction from access token
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m48s
CI / docker (push) Successful in 1m1s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s

The OIDC login flow now reads roles from the access_token (JWT) in
addition to the id_token. This fixes role extraction with providers
like Logto that put scopes/roles in access tokens rather than id_tokens.

- Add audience and additionalScopes to OidcConfig for RFC 8707 resource
  indicator support and configurable extra scopes
- OidcTokenExchanger decodes access_token with at+jwt-compatible processor,
  falls back to id_token if access_token is opaque or has no roles
- syncOidcRoles preserves existing local roles when OIDC returns none
- SPA includes resource and additionalScopes in authorization requests
- Admin UI exposes new config fields

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-07 10:16:52 +02:00
parent 95eb388283
commit 03ff9a3813
11 changed files with 173 additions and 22 deletions

View File

@@ -14,6 +14,8 @@ import java.util.List;
* @param autoSignup whether new OIDC users are automatically created on first login
* @param displayNameClaim dot-separated path to display name in the id_token (e.g. {@code name}, {@code preferred_username})
* @param userIdClaim dot-separated path to user identifier in the id_token (default {@code sub}); e.g. {@code email}, {@code preferred_username}
* @param audience RFC 8707 resource indicator — sent to SPA as {@code resource} param and used for access_token {@code aud} validation
* @param additionalScopes extra scopes the SPA should request beyond {@code openid email profile}
*/
public record OidcConfig(
boolean enabled,
@@ -24,9 +26,11 @@ public record OidcConfig(
List<String> defaultRoles,
boolean autoSignup,
String displayNameClaim,
String userIdClaim
String userIdClaim,
String audience,
List<String> additionalScopes
) {
public static OidcConfig disabled() {
return new OidcConfig(false, "", "", "", "roles", List.of("VIEWER"), true, "name", "sub");
return new OidcConfig(false, "", "", "", "roles", List.of("VIEWER"), true, "name", "sub", "", List.of());
}
}