{"record":{"id":"d86dd16c1675a406","repo":"apereo/cas","slug":"invalid-token-d86dd1","errorCode":null,"errorMessage":"Invalid token: ","messagePattern":"Invalid token: ","errorType":"exception","errorClass":"CredentialsException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20RefreshTokenAuthenticator.java","lineNumber":88,"sourceCode":"        return false;\n    }\n\n    @Override\n    protected void validateCredentials(final UsernamePasswordCredentials credentials,\n                                       final OAuthRegisteredService registeredService,\n                                       final CallContext callContext,\n                                       final OAuth20ClientAuthenticationMethods authnMethod) {\n        val token = credentials.getPassword();\n        LOGGER.trace(\"Received refresh token [{}] for authentication\", token);\n\n        val refreshToken = FunctionUtils.doAndHandle(() -> {\n            val state = getTicketRegistry().getTicket(token, OAuth20RefreshToken.class);\n            return state == null || state.isExpired() ? null : state;\n        });\n        val clientId = credentials.getUsername();\n        if (refreshToken == null || refreshToken.isExpired() || !Strings.CI.equals(refreshToken.getClientId(), clientId)) {\n            LOGGER.error(\"Refresh token [{}] is either not found in the ticket registry, has expired or does not belong to the client [{}]\", token, clientId);\n            throw new CredentialsException(\"Invalid token: \" + token);\n        }\n    }\n}\n","sourceCodeStart":70,"sourceCodeEnd":92,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/authenticator/OAuth20RefreshTokenAuthenticator.java#L70-L92","documentation":"Thrown by the OAuth 2.0 refresh-token authenticator when the submitted refresh_token cannot be validated: it is not found in the ticket registry, has expired, or its clientId does not match the authenticated client. The authenticator returns null instead of an OAuth20RefreshToken in that case and rejects the credentials.","triggerScenarios":"Calling the token endpoint with grant_type=refresh_token where the token was revoked/expired in the ticket registry, the token string is wrong or truncated, or client_id in the request (basic auth or form param) differs from the clientId stored on the refresh token.","commonSituations":"Ticket registry cleanup/eviction (e.g. Redis or in-memory registry restarted, short refresh-token timeout); rotating client credentials and sending the new client_id with an old token; load balancer pointing to a different CAS node backed by a non-shared registry.","solutions":["Confirm the client_id sent with the refresh request exactly matches the one used when the refresh token was issued","Check the refresh token expiration (cas.authn.oauth.refreshToken.timeToKillInSeconds) and registry TTLs; issue a new token via authorization code if expired","Verify all CAS nodes share the same ticket registry (e.g. Redis/Mongo) so tokens issued by one node are visible to others","Log/inspect the token string for truncation or URL-encoding corruption in transit"],"exampleFix":"// before\nPOST /cas/oauth2.0/token grant_type=refresh_token&client_id=NEW_ID&refresh_token=...\n// after\nPOST /cas/oauth2.0/token grant_type=refresh_token&client_id=ORIGINAL_ID&refresh_token=...","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  await refreshToken(clientId, token);\n} catch (e) {\n  if (String(e.message).startsWith('Invalid token')) {\n    // fall back to a full authorization-code flow to obtain a new refresh token\n  }\n}","preventionTips":["Always send the same client_id used at token issuance","Refresh proactively before the token TTL expires","Use a shared, persistent ticket registry in clustered deployments"],"tags":["oauth2","refresh-token","ticket-registry"],"backgroundTag":"record-not-found","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"}