apereo/cas · error
Provided refresh token
Error message
Provided refresh token [{}] does not belong to client [{}] What it means
The validator verifies token-to-client ownership: the OAuth20RefreshToken's clientId must match (case-insensitively) the clientId presented in the token request. A mismatch means the client is trying to refresh a token issued to a different client, so the validator warns and returns false.
Solutions
- Ensure each client refreshes only with tokens issued to its own clientId; clear the client's stored token and re-run the authorization flow.
- Check for shared token storage or copy-pasted tokens across environments/apps.
- Confirm the client_id in the request matches the one used when the refresh token was issued.
- Regenerate the refresh token by authenticating the user again with the correct client.
Example fix
// before POST token: grant_type=refresh_token&client_id=clientB&refresh_token=<token-for-clientA> // after POST token: grant_type=refresh_token&client_id=clientA&refresh_token=<token-for-clientA>
Defensive patterns
Strategy: validation
Validate before calling
// client-side check before refreshing
if (storedToken.clientId !== currentClientId) {
discardStoredToken(); // must re-authenticate
} Type guard
function tokenBelongsToClient(token, clientId) {
return typeof token?.clientId === 'string' &&
token.clientId.toLowerCase() === clientId.toLowerCase();
} Prevention
- Namespace token storage per clientId in each app
- Never share refresh-token caches across environments or clients
- Log clientId alongside token issuance to ease mismatch diagnosis
When it happens
Trigger: Client B presents a refresh token originally issued to client A, typically because multiple OAuth clients share a token store/config or a copy-paste/misconfiguration sends the wrong credentials alongside the token.
Common situations: Two applications accidentally configured with the same refresh-token cache; staging and production clients swapped client secrets; token persisted from a previous deployment with a different clientId; client_id parameter edited while reusing a stored token.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Provided refresh token
- Requested grant type
- Invalid token:
- Subject token type is not supported
- Actor token type is not supported
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/f6e37cb614fa3448.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/token/OAuth20RefreshTokenGrantTypeTokenRequestValidator.java:71
}
LOGGER.debug("Received grant type [{}] with client id [{}]", grantType, clientId);
val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(
configurationContext.getServicesManager(), clientId);
val audit = AuditableContext.builder()
.registeredService(registeredService)
.build();
val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit);
accessResult.throwExceptionIfNeeded();
if (!isGrantTypeSupportedBy(Objects.requireNonNull(registeredService), grantType)) {
LOGGER.warn("Requested grant type [{}] is not authorized by service definition [{}]",
grantType, Objects.requireNonNull(registeredService).getServiceId());
return false;
}
if (refreshToken != null && !Strings.CI.equals(refreshToken.getClientId(), clientId)) {
LOGGER.warn("Provided refresh token [{}] does not belong to client [{}]", refreshToken.getId(), clientId);
return false;
}
return true;
}
@Override
protected OAuth20GrantTypes getGrantType() {
return OAuth20GrantTypes.REFRESH_TOKEN;
}
}
View on GitHub (pinned to e7288fc434)