Skip to content

test format #1

@bkoragan

Description

@bkoragan

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:

  1. Developer triggers GitLab pipeline
  2. Pipeline receives GITLAB_OIDC_TOKEN (ID token with sub = triggering user)
  3. Pipeline sends token exchange request with subject_token_type=urn:ietf:params:oauth:token-type:id_token
  4. 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:

  1. 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).

  2. 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.

  3. 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.

  4. Converter modification — Add id_token to SUPPORTED_TOKEN_TYPES in OAuth2TokenExchangeAuthenticationConverter.

  5. Auto-wiringOAuth2TokenEndpointConfigurer 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 ..

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions