apereo/cas · warning
Provided refresh token
Error message
Provided refresh token [{}] cannot be found in the registry or has expired What it means
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.
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.
Example fix
// before (application.properties) cas.authn.oauth.refresh-token.time-to-kill-in-seconds=3600 // after cas.authn.oauth.refresh-token.time-to-kill-in-seconds=2592000
Defensive patterns
Strategy: try-catch
Try / catch
try {
const tokens = await cas.refreshToken(refreshToken);
} catch (e) {
if (e.status === 400 || e.error === 'invalid_grant') {
// token expired/unknown: fall back to full authorization flow
tokens = await cas.authorizeAndAuthenticate();
} else { throw e; }
} Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Requested grant type
- Provided refresh token
- Invalid token:
- Subject token type is not supported
- Actor token type is not supported
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/f3de9fa67f87d2a5.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oauth-core-api/src/main/java/org/apereo/cas/support/oauth/validator/token/OAuth20RefreshTokenGrantTypeTokenRequestValidator.java:51
protected boolean validateInternal(final WebContext context, final String grantType,
final ProfileManager manager, final UserProfile uProfile) throws Throwable {
val configurationContext = getConfigurationContext().getObject();
val callContext = new CallContext(context, configurationContext.getSessionStore());
val clientId = configurationContext.getRequestParameterResolver()
.resolveClientIdAndClientSecret(callContext).getLeft();
val refreshTokenResult = configurationContext.getRequestParameterResolver()
.resolveRequestParameter(context, OAuth20Constants.REFRESH_TOKEN);
if (refreshTokenResult.isEmpty() || clientId.isEmpty()) {
return false;
}
var refreshToken = (OAuth20RefreshToken) null;
val token = refreshTokenResult.get();
try {
refreshToken = configurationContext.getTicketRegistry().getTicket(token, OAuth20RefreshToken.class);
LOGGER.trace("Found valid refresh token [{}] in the registry", refreshToken);
} catch (final InvalidTicketException e) {
LOGGER.warn("Provided refresh token [{}] cannot be found in the registry or has expired", token);
return false;
}
LOGGER.debug("Received grant type [{}] with client id [{}]", grantType, clientId);
val registeredService = OAuth20Utils.getRegisteredOAuthServiceByClientId(
configurationContext.getServicesManager(), clientId);
val audit = AuditableContext.builder()
.registeredService(registeredService)
.build();
val accessResult = configurationContext.getRegisteredServiceAccessStrategyEnforcer().execute(audit);
accessResult.throwExceptionIfNeeded();
if (!isGrantTypeSupportedBy(Objects.requireNonNull(registeredService), grantType)) {
LOGGER.warn("Requested grant type [{}] is not authorized by service definition [{}]",
grantType, Objects.requireNonNull(registeredService).getServiceId());
return false;
}
View on GitHub (pinned to e7288fc434)