Repository navigation
Conversation
…er of that zone When an OIDC identity provider points at the token endpoint of the zone itself, the id_token is verified with the keys of the zone. A zone without keys of its own uses the keys of the default zone, so the signature alone does not tell which zone issued the token. The issuer of the token is now checked against the issuer of the identity provider. This is the check that access and refresh tokens, and the other branch of this method, already make. A token issued for another zone, or a token without an iss claim, is rejected. Tokens of the zone are unchanged, because they carry the issuer of the zone.
… grant with the generic bad credentials error When the identity provider of a password grant rejects the request with a 4xx response, the whole body of that response was returned to the caller as the error_description. The caller now receives the same answer as for wrong client credentials: 401, invalid_client, "Bad credentials". The status and the body of the identity provider are written to the log at DEBUG. When the identity provider fails with a 5xx response, the caller receives 500 with the error server_error and the text of the identity provider as the error_description. The body of that response was empty before. The behavior is described in the documentation of the token endpoint.
The id_token of a password grant is fetched by UAA from the identity provider itself, so it is not bound to the audience of a relying party again, and the identity provider is resolved from the issuer of the token. This is intended. The test documents it.
There was a problem hiding this comment.
🟡 Changes recommended
Token endpoint documentation is incomplete because it omits that UAA exposes the HTTP status text when the provider returns an empty error body.
1 open issue
What changed in this PR
This pull request hardens OIDC token validation and standardizes password-grant error handling for external identity providers.
Changes:
- Requires self-issued UAA ID tokens to contain the current zone’s issuer.
- Maps provider 4xx responses to generic invalid_client/Bad credentials errors and provider 5xx responses to server_error.
- Adds unit, integration, and token endpoint documentation coverage.
| File | Description |
|---|---|
| uaa/src/test/java/org/cloudfoundry/identity/uaa/login/TokenEndpointDocs.java | Documents OIDC provider error responses for password grants. |
| uaa/src/test/java/org/cloudfoundry/identity/uaa/integration/feature/PasswordGrantIT.java | Adds end-to-end coverage for provider rejection and failure responses. |
| server/src/test/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManagerTest.java | Tests issuer enforcement and password-grant audience behavior. |
| server/src/test/java/org/cloudfoundry/identity/uaa/authentication/manager/PasswordGrantAuthenticationManagerTest.java | Tests provider error mapping, logging, fallback status text, and origin handling. |
| server/src/main/java/org/cloudfoundry/identity/uaa/provider/oauth/ExternalOAuthAuthenticationManager.java | Adds issuer validation for self-referencing UAA OIDC providers. |
| server/src/main/java/org/cloudfoundry/identity/uaa/authentication/manager/PasswordGrantAuthenticationManager.java | Normalizes provider 4xx errors and maps provider 5xx failures to OAuth server errors. |
Review effort: Lite
…back for empty provider error bodies' Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟢 Approval recommended
No active review issues remain, with coverage added for issuer enforcement and the updated OAuth error handling across unit, integration, and documentation tests.
No open issues 🎉
1 resolved since last review
Review effort: Lite
| throw new BadCredentialsException("Bad credentials", e); | ||
| } catch (HttpServerErrorException e) { | ||
| // a failure of the provider itself is passed on to the client, with the text of the provider | ||
| String description = StringUtils.hasText(e.getResponseBodyAsString()) ? e.getResponseBodyAsString() : e.getStatusText(); |
There was a problem hiding this comment.
Could we return a generic server error here? The provider’s response body can contain internal diagnostics that would be exposed to the password grant client. For example, a provider error containing Database connection failed: db.internal.example:5432 would be returned in error_description.

Summary
Two changes to how UAA handles tokens and errors around OIDC identity providers:
provider.
Issuer of an id_token of the zone's own token endpoint
When an OIDC identity provider points at the token endpoint of the zone itself, the id_token is verified with the keys of the zone. A zone without keys of its own uses the keys of the default zone, so the signature alone does not
tell which zone issued the token.
ExternalOAuthAuthenticationManager.validateTokennow chainscheckIssuer(config.getIssuer())on that branch. A token issued for another zone, or a token without anissclaim, is rejected.This is the check that already applies to access and refresh tokens (
TokenValidationService), toresolveOriginProvider, and to the other branch ofvalidateToken. Tokens of the zone are unchanged, because they carry theissuer of the zone. After the
issuer.urior the issuer of a zone is changed, tokens issued under the old value are no longer accepted on this path, as they are not after a rotation of the signing keys.Errors of the identity provider on the OIDC password grant
PasswordGrantAuthenticationManager.oidcPasswordGrant:401,invalid_client,error_descriptionis the whole body of the identity provider401,invalid_client,error_descriptionisBad credentials, the same as for wrong client credentials. Status and body of the500with an empty body500,server_error,error_descriptionis the text of the identity provider (the status text if the body is empty)The error code stays
invalid_clientfor a 4xx, so a caller cannot tell which part of the request failed. A 5xx stays visible as a 500.The behavior is described in the documentation of the token endpoint (
TokenEndpointDocs, on thegrant_typeparameter of the password grant).Password grant and the audience of the id_token
A test documents that the password grant hands no origin to the external manager. The id_token is fetched by UAA from the identity provider itself, so it is not bound to the audience of a relying party again, and the identity
provider is resolved from the issuer of the token. This is the intended behavior and is unchanged.
Tests
Tests
ExternalOAuthAuthenticationManagerTestgetExternalAuthenticationDetails_whenUaaTokenHasAnotherIssuer_throws: a token signed with the key of the zone, bound to the relying party, but issued by another zone, is rejected.verifySubjectToken_whenUaaTokenHasTheProviderIssuerAndAnotherAudience_stillVerifies: a token with the issuer of the provider and another audience still verifies.PasswordGrantAuthenticationManagerTest: a 4xx givesBad credentials; the body of the identity provider reaches the DEBUG log; a 5xx givesserver_errorwith the text of the identity provider, or with the status text if thebody is empty; no origin is handed to the external manager.
PasswordGrantIT, with a stand-in identity provider that answers every request with a chosen response:401,invalid_client,Bad credentials, and the host name is not in the response;500,server_errorand that text.