feat: token exchange for UAA-issued opaque access tokens#3845
feat: token exchange for UAA-issued opaque access tokens#3845mikeroda wants to merge 3 commits intocloudfoundry:developfrom
Conversation
mikeroda
commented
Apr 16, 2026
- When performing a token exchange grant, if the subject_token is not a JWT, attempt to look it up from the revocable tokens.
- When performing a token exchange grant, if the subject_token is not a JWT, attempt to look it up from the revocable tokens.
There was a problem hiding this comment.
Pull request overview
Adds support for token exchange when subject_token is an opaque (non-JWT) access token issued by UAA, by resolving it from the revocable token store before decoding claims.
Changes:
- Add opaque-token resolution to token exchange processing (revocable token store lookup when
subject_tokenis not a JWT). - Wire
RevocableTokenProvisioninginto token exchange components via Spring configuration. - Extend MockMvc/integration and unit tests to cover opaque access token exchange flows.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeMockMvcBase.java | Adds helper overloads to request different token formats (JWT vs opaque) in token exchange/JWT-bearer grant tests. |
| uaa/src/test/java/org/cloudfoundry/identity/uaa/mock/token/TokenExchangeDefaultConfigMockMvcTests.java | Adds integration-style test covering opaque access token as subject_token in a subsequent exchange. |
| server/src/test/java/org/cloudfoundry/identity/uaa/oauth/token/TokenExchangeGranterTests.java | Adds unit tests for resolving opaque subject_token via RevocableTokenProvisioning. |
| server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java | Introduces an unused import/whitespace change (needs cleanup). |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/token/TokenExchangeGranter.java | Implements opaque token lookup from revocable token store before decoding JWT claims. |
| server/src/main/java/org/cloudfoundry/identity/uaa/oauth/provider/config/xml/OAuth2FilterConfig.java | Injects RevocableTokenProvisioning into token exchange granter and token endpoint filter wiring. |
| server/src/main/java/org/cloudfoundry/identity/uaa/authentication/BackwardsCompatibleTokenEndpointAuthenticationFilter.java | Resolves opaque access-token subject_token to backing JWT before passing token exchange downstream. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // A real JWT signed with a trivial key — three dot-separated parts | ||
| String jwtValue = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiaXNzIjoiaHR0cHM6Ly91YWEuZXhhbXBsZS5jb20iLCJ1c2VyX25hbWUiOiJqb2huIiwidXNlcl9pZCI6InVzZXIxMjMiLCJvcmlnaW4iOiJ1YWEifQ.signature"; |
There was a problem hiding this comment.
The JWT test fixture value for jwtValue is not guaranteed to be parseable by Nimbus JWTParser.parse(...) because the signature segment ("signature") is not valid Base64URL (length mod 4 = 1). This can cause JwtHelper.decode(jwtValue) to throw InvalidTokenException and make the test flaky/fail. Use a fully valid 3-part Base64URL token (all segments Base64URL-decodable) or generate one via existing helpers (e.g., UaaTokenUtils.constructToken(...) / JwtHelper.encode(...)) so the parser reliably accepts it.
| // A real JWT signed with a trivial key — three dot-separated parts | |
| String jwtValue = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiaXNzIjoiaHR0cHM6Ly91YWEuZXhhbXBsZS5jb20iLCJ1c2VyX25hbWUiOiJqb2huIiwidXNlcl9pZCI6InVzZXIxMjMiLCJvcmlnaW4iOiJ1YWEifQ.signature"; | |
| // A parseable JWT fixture with three Base64URL-encoded parts | |
| String jwtValue = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiaXNzIjoiaHR0cHM6Ly91YWEuZXhhbXBsZS5jb20iLCJ1c2VyX25hbWUiOiJqb2huIiwidXNlcl9pZCI6InVzZXIxMjMiLCJvcmlnaW4iOiJ1YWEifQ.c2lnbmF0dXJl"; |