{"record":{"id":"4c983ae1b0add427","repo":"apereo/cas","slug":"invalid-request-4c983a","errorCode":"invalid_request","errorMessage":"Provided token [{}] has not been issued for the service [{}]","messagePattern":"Provided token \\[(.+?)\\] has not been issued for the service \\[(.+?)\\]","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20RevocationEndpointController.java","lineNumber":110,"sourceCode":"    }\n\n    protected ModelAndView generateRevocationResponse(final String token,\n                                                      final String clientId,\n                                                      final HttpServletResponse response) throws Exception {\n        val registryToken = FunctionUtils.doAndHandle(() -> {\n            val state = getConfigurationContext().getTicketRegistry().getTicket(token, OAuth20Token.class);\n            return state == null || state.isExpired() ? null : state;\n        });\n        if (registryToken == null) {\n            LOGGER.error(\"Provided token [{}] has not been found in the ticket registry\", token);\n            val mv = new ModelAndView(new JacksonJsonView());\n            mv.setStatus(HttpStatus.NOT_FOUND);\n            return mv;\n        }\n        \n        if (isRefreshToken(registryToken) || isAccessToken(registryToken)) {\n            if (!Strings.CI.equals(clientId, registryToken.getClientId())) {\n                LOGGER.warn(\"Provided token [{}] has not been issued for the service [{}]\", token, clientId);\n                return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);\n            }\n            if (isRefreshToken(registryToken)) {\n                revokeToken((OAuth20RefreshToken) registryToken);\n            } else {\n                revokeToken(registryToken.getId());\n            }\n            val mv = new ModelAndView(new JacksonJsonView());\n            mv.setStatus(HttpStatus.OK);\n            return mv;\n        }\n        LOGGER.error(\"Provided token [{}] is either not a refresh token or an access token\", token);\n        return OAuth20Utils.writeError(response, OAuth20Constants.INVALID_REQUEST);\n\n    }\n\n    private boolean verifyRevocationRequest(final WebContext context) throws Throwable {\n        val validator = getConfigurationContext().getAccessTokenGrantRequestValidators().getObject()","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/web/endpoints/OAuth20RevocationEndpointController.java#L92-L128","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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."],"exampleFix":"// before\ncurl -u wrong-client:secret -d 'token=AT-1234...' https://cas/oauth2.0/revoke\n// after\ncurl -u correct-client:secret -d 'token=AT-1234...' https://cas/oauth2.0/revoke","handlingStrategy":"validation","validationCode":"// client side: only revoke tokens issued to you\nif (token.clientId !== myClientId) {\n  throw new Error(`Token ${token.id} belongs to client ${token.clientId}, not ${myClientId}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["oauth2","revocation","client-mismatch","token"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}