Skip to content

Add an extra subject_token verification to protect token extensions #3954

Open
fhanik wants to merge 3 commits into
developfrom
pr/harden-token-exchange-validation
Open

Add an extra subject_token verification to protect token extensions #3954
fhanik wants to merge 3 commits into
developfrom
pr/harden-token-exchange-validation

Conversation

@fhanik

@fhanik fhanik commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • TokenExchangeGranter.getTokenActor() now calls
    ExternalOAuthAuthenticationManager.verifySubjectToken() instead of the
    parse-only JwtHelper.decode(). The new method resolves the registered
    identity provider by the token's iss claim, verifies the JWT signature
    against that provider's published keys, and checks expiry — reusing logic
    already present in ExternalOAuthAuthenticationManager.
  • OAuth2FilterConfig wires the existing externalOAuthAuthenticationManager
    bean into TokenExchangeGranter as a new constructor parameter.
  • Two new MockMvc tests cover the granter-level check end-to-end:
    TokenExchangeDefaultConfigMockMvcTests confirms the default configuration
    rejects a tampered token at the filter layer (HTTP 401); the new
    TokenExchangeSubjectTokenSignatureBypassMockMvcTests confirms the granter
    independently rejects it (HTTP 400 invalid_grant) even when the filter
    layer is replaced by a custom bean that skips signature checking.

…ng subject_token verification

TokenExchangeGranter.getTokenActor() previously parsed the subject_token with JwtHelper.decode(), a parse-only operation that never verified the JWT signature. Because the default tokenExchangeAuthenticationManager is @ConditionalOnMissingBean, a custom replacement that weakens or omits signature verification left getTokenActor() as an unguarded path: claims from an unverified — or tampered — subject_token would be trusted and embedded verbatim in the act claim of the issued token.

A new public method, ExternalOAuthAuthenticationManager.verifySubjectToken(), encapsulates the existing issuer-based IdP resolution (resolveOriginProvider, driven by the token's iss claim) and signature/expiry validation (validateToken) into a single callable that getTokenActor() now invokes. Any failure — unknownissuer, signature mismatch, or expired token — is converted to InvalidGrantException and surfaced as HTTP 400. The granter's verification isindependent of the filter layer, so it holds regardless of which tokenExchangeAuthenticationManager bean is active.
@fhanik fhanik changed the title Protect token exchange extensions from exposing themselves from lacki… Add an extra subject_token verification to protect token extensions Jun 18, 2026
@strehle strehle requested a review from Copilot June 19, 2026 05:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens OAuth2 Token Exchange by ensuring TokenExchangeGranter independently verifies the subject_token JWT signature (and expiry) based on the issuer-mapped registered IdP, preventing signature-check bypasses when the filter-layer authentication manager is overridden.

Changes:

  • Added ExternalOAuthAuthenticationManager.verifySubjectToken() to resolve IdP by iss, verify signature, and check expiry.
  • Updated TokenExchangeGranter to call verifySubjectToken() (instead of parse-only decoding) and wired the needed bean via OAuth2FilterConfig.
  • Added unit and MockMvc integration tests to prove tampered subject_tokens are rejected both in the default setup and when the filter layer skips signature validation.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeSubjectTokenSignatureBypassMockMvcTests.java New integration test proving granter-level verification rejects tampered tokens even if filter-layer verification is bypassed via bean override.
uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeDefaultConfigMockMvcTests.java Adds default-configuration integration coverage showing tampered subject_token is blocked at the filter layer (401).
server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenExchangeGranterTests.java Updates unit tests for new granter dependency and adds signature-verification failure cases.
server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java Introduces verifySubjectToken() API to reuse existing issuer resolution + signature/expiry validation logic.
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenExchangeGranter.java Replaces parse-only JWT decoding with signature+expiry verification for subject_token.
server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/config/xml/OAuth2FilterConfig.java Wires externalOAuthAuthenticationManager into TokenExchangeGranter via constructor injection.

fhanik and others added 2 commits June 19, 2026 06:45
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants