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,5 +14,7 @@ public record OidcAdminConfigRequest(
List<String> defaultRoles,
boolean autoSignup,
String displayNameClaim,
String userIdClaim
String userIdClaim,
String audience,
List<String> additionalScopes
) {}

View File

@@ -17,10 +17,12 @@ public record OidcAdminConfigResponse(
List<String> defaultRoles,
boolean autoSignup,
String displayNameClaim,
String userIdClaim
String userIdClaim,
String audience,
List<String> additionalScopes
) {
public static OidcAdminConfigResponse unconfigured() {
return new OidcAdminConfigResponse(false, false, null, null, false, null, null, false, null, null);
return new OidcAdminConfigResponse(false, false, null, null, false, null, null, false, null, null, null, null);
}
public static OidcAdminConfigResponse from(OidcConfig config) {
@@ -28,7 +30,7 @@ public record OidcAdminConfigResponse(
true, config.enabled(), config.issuerUri(), config.clientId(),
!config.clientSecret().isBlank(), config.rolesClaim(),
config.defaultRoles(), config.autoSignup(), config.displayNameClaim(),
config.userIdClaim()
config.userIdClaim(), config.audience(), config.additionalScopes()
);
}
}

View File

@@ -9,5 +9,9 @@ public record OidcPublicConfigResponse(
@NotNull String clientId,
@NotNull String authorizationEndpoint,
@Schema(description = "Present if the provider supports RP-initiated logout")
String endSessionEndpoint
String endSessionEndpoint,
@Schema(description = "RFC 8707 resource indicator for the authorization request")
String resource,
@Schema(description = "Additional scopes to request beyond openid email profile")
java.util.List<String> additionalScopes
) {}