{"record":{"id":"f3de9fa67f87d2a5","repo":"apereo/cas","slug":"provided-refresh-token-cannot-be-found-in-the","errorCode":null,"errorMessage":"Provided refresh token [{}] cannot be found in the registry or has expired","messagePattern":"Provided refresh token \\[(.+?)\\] cannot be found in the registry or has expired","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/token/OAuth20RefreshTokenGrantTypeTokenRequestValidator.java","lineNumber":51,"sourceCode":"    protected boolean validateInternal(final WebContext context, final String grantType,\n                                       final ProfileManager manager, final UserProfile uProfile) throws Throwable {\n        val configurationContext = getConfigurationContext().getObject();\n        val callContext = new CallContext(context, configurationContext.getSessionStore());\n        val clientId = configurationContext.getRequestParameterResolver()\n            .resolveClientIdAndClientSecret(callContext).getLeft();\n        val refreshTokenResult = configurationContext.getRequestParameterResolver()\n            .resolveRequestParameter(context, OAuth20Constants.REFRESH_TOKEN);\n        if (refreshTokenResult.isEmpty() || clientId.isEmpty()) {\n            return false;\n        }\n\n        var refreshToken = (OAuth20RefreshToken) null;\n        val token = refreshTokenResult.get();\n        try {\n            refreshToken = configurationContext.getTicketRegistry().getTicket(token, OAuth20RefreshToken.class);\n            LOGGER.trace(\"Found valid refresh token [{}] in the registry\", refreshToken);\n        } catch (final InvalidTicketException e) {\n            LOGGER.warn(\"Provided refresh token [{}] cannot be found in the registry or has expired\", token);\n            return false;\n        }\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","sourceCodeStart":33,"sourceCodeEnd":69,"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#L33-L69","documentation":"OAuth20RefreshTokenGrantTypeTokenRequestValidator retrieves the presented refresh_token from the TicketRegistry as an OAuth20RefreshToken. If the ticket is unknown or has expired, getTicket throws InvalidTicketException; the validator catches it, logs this warning, and returns false so the refresh grant fails.","triggerScenarios":"A token request with grant_type=refresh_token supplies a refresh token string that no longer exists in the ticket registry (expired TTL, registry restart with an in-memory registry, eviction, or a fabricated/typo'd token).","commonSituations":"Refresh token lifetime (cas.authn.oauth.refresh-token.timeToKillInSeconds) shorter than the client's reuse window; CAS restarted with the default in-memory ticket registry; ticket-registry backend (Redis/Hazelcast/JDBC) evicted or flushed entries; clock skew causing early expiry.","solutions":["Increase the refresh token expiration (cas.authn.oauth.refresh-token.time-to-kill-in-seconds) to cover the client's refresh interval.","Configure a persistent ticket registry (Redis, Hazelcast, JDBC) so tokens survive CAS restarts.","Have the client handle this failure by re-running the full authorization flow to obtain a new refresh token.","Check the ticket registry backend for eviction/cleanup settings that may purge tickets prematurely."],"exampleFix":"// before (application.properties)\ncas.authn.oauth.refresh-token.time-to-kill-in-seconds=3600\n// after\ncas.authn.oauth.refresh-token.time-to-kill-in-seconds=2592000","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  const tokens = await cas.refreshToken(refreshToken);\n} catch (e) {\n  if (e.status === 400 || e.error === 'invalid_grant') {\n    // token expired/unknown: fall back to full authorization flow\n    tokens = await cas.authorizeAndAuthenticate();\n  } else { throw e; }\n}","preventionTips":["Use a persistent ticket registry (Redis/Hazelcast/JDBC) so tokens survive restarts","Set refresh-token TTL comfortably above the client's refresh interval","Implement client-side fallback to re-authentication when refresh fails"],"tags":["oauth","refresh-token","ticket-expired","token-registry"],"backgroundTag":"jwt-token-expired","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"}