{"record":{"id":"f6e37cb614fa3448","repo":"apereo/cas","slug":"provided-refresh-token-does-not-belong-to-cli","errorCode":null,"errorMessage":"Provided refresh token [{}] does not belong to client [{}]","messagePattern":"Provided refresh token \\[(.+?)\\] does not belong to client \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/token/OAuth20RefreshTokenGrantTypeTokenRequestValidator.java","lineNumber":71,"sourceCode":"        }\n\n        LOGGER.debug(\"Received grant type [{}] with client id [{}]\", grantType, clientId);\n        val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(\n            configurationContext.getServicesManager(), clientId);\n        val audit = AuditableContext.builder()\n            .registeredService(registeredService)\n            .build();\n        val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit);\n        accessResult.throwExceptionIfNeeded();\n\n        if (!isGrantTypeSupportedBy(Objects.requireNonNull(registeredService), grantType)) {\n            LOGGER.warn(\"Requested grant type [{}] is not authorized by service definition [{}]\",\n                grantType, Objects.requireNonNull(registeredService).getServiceId());\n            return false;\n        }\n\n        if (refreshToken != null && !Strings.CI.equals(refreshToken.getClientId(), clientId)) {\n            LOGGER.warn(\"Provided refresh token [{}] does not belong to client [{}]\", refreshToken.getId(), clientId);\n            return false;\n        }\n\n        return true;\n    }\n\n    @Override\n    protected OAuth20GrantTypes getGrantType() {\n        return OAuth20GrantTypes.REFRESH_TOKEN;\n    }\n}\n","sourceCodeStart":53,"sourceCodeEnd":83,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/token/OAuth20RefreshTokenGrantTypeTokenRequestValidator.java#L53-L83","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nPOST token: grant_type=refresh_token&client_id=clientB&refresh_token=<token-for-clientA>\n// after\nPOST token: grant_type=refresh_token&client_id=clientA&refresh_token=<token-for-clientA>","handlingStrategy":"validation","validationCode":"// client-side check before refreshing\nif (storedToken.clientId !== currentClientId) {\n  discardStoredToken(); // must re-authenticate\n}","typeGuard":"function tokenBelongsToClient(token, clientId) {\n  return typeof token?.clientId === 'string' &&\n    token.clientId.toLowerCase() === clientId.toLowerCase();\n}","tryCatchPattern":null,"preventionTips":["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"],"tags":["oauth","refresh-token","client-mismatch","token-theft"],"backgroundTag":"invalid-argument-value","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"}