apereo/cas · warning
invalid_request
invalid_request
Error message
Provided token [{}] has not been issued for the service [{}] What it means
During OAuth2 token revocation (RFC 7009), CAS looks up the token in the ticket registry and checks that its client_id matches the client_id authenticated on the revocation request. If the token belongs to a different client, CAS refuses to revoke it and returns an `invalid_request` error with HTTP 404-style NOT_FOUND handling, per the spec's requirement that clients only revoke their own tokens.
Solutions
- Ensure the revocation request uses the same client_id (and secret) that was used when the token was issued.
- Locate the token in the ticket registry and check its client_id; issue a new token from the correct client if needed.
- If tokens were migrated or the service was re-registered, purge stale tokens for the old client_id from the registry.
- Confirm the correct client credentials are being sent (Basic auth or body params) and not cached credentials from another client.
Example fix
// before curl -u wrong-client:secret -d 'token=AT-1234...' https://cas/oauth2.0/revoke // after curl -u correct-client:secret -d 'token=AT-1234...' https://cas/oauth2.0/revoke
Defensive patterns
Strategy: validation
Validate before calling
// client side: only revoke tokens issued to you
if (token.clientId !== myClientId) {
throw new Error(`Token ${token.id} belongs to client ${token.clientId}, not ${myClientId}`);
} Prevention
- Store tokens per client_id and never share tokens across clients.
- Use the same credential store for token issuance and revocation.
- On 404/invalid_request from revoke, check for cross-client token usage in logs.
When it happens
Trigger: A POST to the /oauth2.0/revoke endpoint (handleRequest -> generateRevocationResponse) where the `token` parameter resolves to an access or refresh token in the registry whose getClientId() differs (case-insensitively) from the authenticated `client_id` of the caller.
Common situations: Two OAuth clients configured in the same CAS server where a client accidentally sends another client's token; copying a token from logs/tests belonging to a different service; client_id mismatch after re-registering a service with a new client_id while old tokens persist in the registry (e.g. in-memory vs redis/jpa registry).
Related errors
- access_denied
- Ignoring malformed request
- Failed to acquire access token
- Invalid client credentials provided for registered service:
- Code verification method is unrecognized:
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/4c983ae1b0add427.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20RevocationEndpointController.java:110
}
protected ModelAndView generateRevocationResponse(final String token,
final String clientId,
final HttpServletResponse response) throws Exception {
val registryToken = FunctionUtils.doAndHandle(() -> {
val state = getConfigurationContext().getTicketRegistry().getTicket(token, OAuth20Token.class);
return state == null || state.isExpired() ? null : state;
});
if (registryToken == null) {
LOGGER.error("Provided token [{}] has not been found in the ticket registry", token);
val mv = new ModelAndView(new JacksonJsonView());
mv.setStatus(HttpStatus.NOT_FOUND);
return mv;
}
if (isRefreshToken(registryToken) || isAccessToken(registryToken)) {
if (!Strings.CI.equals(clientId, registryToken.getClientId())) {
LOGGER.warn("Provided token [{}] has not been issued for the service [{}]", token, clientId);
return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);
}
if (isRefreshToken(registryToken)) {
revokeToken((OAuth20RefreshToken) registryToken);
} else {
revokeToken(registryToken.getId());
}
val mv = new ModelAndView(new JacksonJsonView());
mv.setStatus(HttpStatus.OK);
return mv;
}
LOGGER.error("Provided token [{}] is either not a refresh token or an access token", token);
return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);
}
private boolean verifyRevocationRequest(final WebContext context) throws Throwable {
val validator = getConfigurationContext().getAccessTokenGrantRequestValidators().getObject()View on GitHub (pinned to e7288fc434)