Summary
When rotating a UAA client secret (adding a new secret, then deleting the original one), subsequent refresh_token grant calls fail with invalid_client: Bad credentials, even though client_id remains unchanged throughout. refresh_token is bound to the client's identity (client_id), and client authentication should be validated independently at each /oauth/token call using whatever secret is currently valid for that client — the secret rotation itself should not invalidate previously issued refresh tokens. This forces users to be re-authenticated (effectively logged out) after every completed secret rotation, defeating the purpose of zero-downtime rotation.
Environment
Steps to Reproduce
- Generate an access_token and refresh_token via /oauth/token using grant_type=password (or authorization_code), passing client_id and client_secret (Secret A).
- Add a new secret to the same client: POST /oauth/clients/{client_id}/secret?changeMode=ADD (Secret B)
- Remove the original secret: POST /oauth/clients/{client_id}/secret?changeMode=DELETE (removing Secret A) (This happens on next secret rotation)
- Call the token endpoint using the refresh token from step 1, authenticating with the new secret (Secret B): POST /oauth/token grant_type=refresh_token&refresh_token=...&client_id=...&client_secret=
Expected Behavior
Since client_id has not changed and the caller is authenticating with a currently valid secret for that client, the refresh_token grant should succeed and return a new access_token/refresh_token pair.
Actual Behavior
The request fails with:
json
{
"error": "invalid_client",
"error_description": "Bad credentials"
}
This happens even though the client authentication itself (client_id + current secret) is valid — it appears the refresh_token is internally tied to the specific secret value active at the time of issuance, rather than to the client's identity alone.
Related
This is related to #4047, which reports a similar failure during the ADD phase of rotation (before the old secret is deleted). Together these suggest refresh_token validation in UAA may be coupling client authentication to a specific secret value rather than re-validating client identity independently on each call.
Summary
When rotating a UAA client secret (adding a new secret, then deleting the original one), subsequent
refresh_tokengrant calls fail withinvalid_client: Bad credentials, even though client_id remains unchanged throughout. refresh_token is bound to the client's identity (client_id), and client authentication should be validated independently at each /oauth/token call using whatever secret is currently valid for that client — the secret rotation itself should not invalidate previously issued refresh tokens. This forces users to be re-authenticated (effectively logged out) after every completed secret rotation, defeating the purpose of zero-downtime rotation.Environment
Steps to Reproduce
Expected Behavior
Since client_id has not changed and the caller is authenticating with a currently valid secret for that client, the refresh_token grant should succeed and return a new access_token/refresh_token pair.
Actual Behavior
The request fails with:
json
{
"error": "invalid_client",
"error_description": "Bad credentials"
}
This happens even though the client authentication itself (client_id + current secret) is valid — it appears the refresh_token is internally tied to the specific secret value active at the time of issuance, rather than to the client's identity alone.
Related
This is related to #4047, which reports a similar failure during the ADD phase of rotation (before the old secret is deleted). Together these suggest refresh_token validation in UAA may be coupling client authentication to a specific secret value rather than re-validating client identity independently on each call.