I'd like to propose an approach for this enhancement and would appreciate feedback before opening a PR.
Some of Use Cases
1. CI/CD Pipeline Identity (Primary — from this issue)
GitLab, GitHub Actions, and other CI platforms issue OIDC ID tokens representing the user who triggered the pipeline. Organizations want to exchange these ID tokens for access tokens to maintain per-user audit trails, replacing shared client_credentials that obscure who performed an action.
Example flow:
- Developer triggers GitLab pipeline
- Pipeline receives
GITLAB_OIDC_TOKEN (ID token with sub = triggering user)
- Pipeline sends token exchange request with
subject_token_type=urn:ietf:params:oauth:token-type:id_token
- Spring Authorization Server validates the ID token via GitLab's JWKS endpoint and issues an access token attributed to the user.
2. Federated Identity
An organization trusts multiple external IdPs (Google, Azure AD, Okta). Services present ID tokens from these IdPs and exchange them for local access tokens scoped to the authorization servers' resource modell.
3. Backend-to-Backend with User Context
A backend service receives a user's ID token from a frontend and needs to exchange it for an access token to call downstream services on behalf of that user, without requiring a full authorization code flow.
RFC Basis
RFC 8693 Section 3 explicitly defines urn:ietf:params:oauth:token-type:id_token as a valid token type idntifier. The current implementation only supportsaccess_token and jwt types.
Here is proposed approach
Since OAuth2TokenExchangeAuthenticationProvider is final, I think of a pluggable resolver strategy rather than opening the class for extension:
-
OAuth2TokenExchangeSubjectTokenResolver — keeping the @FunctionalInterface strategy that resolves a subject token + type into a principal context. Returns null if it cannot handle the token type (chain-of-responsibility).
-
OidcIdTokenSubjectTokenResolver — default implementation that uses JwtDecoderFactory<RegisteredClient> to decode and validate externally-issued ID tokens. This follows the same pattern asJwtClientAssertionDecoderFactory in the same module.
-
Provider modification — Add a setSubjectTokenResolver() setter on OAuth2TokenExchangeAuthenticationProvider. When set, the resolver is tried first; if it returns null, the existing authorizationService.findByToken() path is used. Full backward compatibility is preserved.
-
Converter modification — Add id_token to SUPPORTED_TOKEN_TYPES in OAuth2TokenExchangeAuthenticationConverter.
-
Auto-wiring — OAuth2TokenEndpointConfigurer picks up aOAuth2TokenExchangeSubjectTokenResolver bean via getOptionalBean(same pattern as OAuth2AuthorizationService, OAuth2TokenGenerator, etc.).
Notes:
- No breaking changes — Existing behavior is untouched when no resolver is configured
- Follows existing patterns —
@FunctionalInterface strategy,JwtDecoderFactory, getOptionalBean auto-wiring
- Keeps
final — The provider class stays final; only the token resolution strategy is pluggable
- Extensible — Users can implement their own resolver for non-OIDC external tokens (e.g., custom JWT formats)
Configuration Example
@Bean
OidcIdTokenSubjectTokenResolver subjectTokenResolver() {
return new OidcIdTokenSubjectTokenResolver((registeredClient) -> {
// create an JwtDecoder per client pointing to the trusted
// IDP's JWKS endpoint
String jwkSetUri = registeredClient.getClientSettings()
.getSetting("id-token-jwk-set-uri");
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).build();
});
}
Happy to adjust the approach based on your feedback before opening a PR ..
I'd like to propose an approach for this enhancement and would appreciate feedback before opening a PR.
Some of Use Cases
1. CI/CD Pipeline Identity (Primary — from this issue)
GitLab, GitHub Actions, and other CI platforms issue OIDC ID tokens representing the user who triggered the pipeline. Organizations want to exchange these ID tokens for access tokens to maintain per-user audit trails, replacing shared
client_credentialsthat obscure who performed an action.Example flow:
GITLAB_OIDC_TOKEN(ID token withsub= triggering user)subject_token_type=urn:ietf:params:oauth:token-type:id_token2. Federated Identity
An organization trusts multiple external IdPs (Google, Azure AD, Okta). Services present ID tokens from these IdPs and exchange them for local access tokens scoped to the authorization servers' resource modell.
3. Backend-to-Backend with User Context
A backend service receives a user's ID token from a frontend and needs to exchange it for an access token to call downstream services on behalf of that user, without requiring a full authorization code flow.
RFC Basis
RFC 8693 Section 3 explicitly defines
urn:ietf:params:oauth:token-type:id_tokenas a valid token type idntifier. The current implementation only supportsaccess_tokenandjwttypes.Here is proposed approach
Since
OAuth2TokenExchangeAuthenticationProviderisfinal, I think of a pluggable resolver strategy rather than opening the class for extension:OAuth2TokenExchangeSubjectTokenResolver— keeping the@FunctionalInterfacestrategy that resolves a subject token + type into a principal context. Returnsnullif it cannot handle the token type (chain-of-responsibility).OidcIdTokenSubjectTokenResolver— default implementation that usesJwtDecoderFactory<RegisteredClient>to decode and validate externally-issued ID tokens. This follows the same pattern asJwtClientAssertionDecoderFactoryin the same module.Provider modification — Add a
setSubjectTokenResolver()setter onOAuth2TokenExchangeAuthenticationProvider. When set, the resolver is tried first; if it returnsnull, the existingauthorizationService.findByToken()path is used. Full backward compatibility is preserved.Converter modification — Add
id_tokentoSUPPORTED_TOKEN_TYPESinOAuth2TokenExchangeAuthenticationConverter.Auto-wiring —
OAuth2TokenEndpointConfigurerpicks up aOAuth2TokenExchangeSubjectTokenResolverbean viagetOptionalBean(same pattern asOAuth2AuthorizationService,OAuth2TokenGenerator, etc.).Notes:
@FunctionalInterfacestrategy,JwtDecoderFactory,getOptionalBeanauto-wiringfinal— The provider class staysfinal; only the token resolution strategy is pluggableConfiguration Example