apereo/cas · error · InvalidTicketException
Service ticket [ ] does not exist.
Error message
Service ticket [{}] does not exist. What it means
DefaultCentralAuthenticationService.validateServiceTicket fetches the ticket id from the ticket registry; if no ServiceTicket with that id exists, it logs a warning and throws InvalidTicketException. This also covers tickets that expired and were purged from the registry.
Solutions
- Validate the ST exactly once and immediately; obtain a new one by re-authenticating the user if needed
- Increase cas.ticket.st.time-to-kill-in-seconds if clients legitimately need longer validation windows
- Verify all CAS nodes share the same ticket registry backend (e.g. same Redis/Hazelcast/JDBC store)
- Confirm ticket cleanup/purge jobs are not evicting live tickets prematurely
Example fix
// before
try { st = cas.validateServiceTicket(ticketId, service); }
// after (handle one-time-use ST)
try { st = cas.validateServiceTicket(ticketId, service); }
catch (InvalidTicketException e) {
// ST is single-use/expired: redirect user back to CAS for a fresh ticket
response.sendRedirect(loginUrl + "?service=" + encode(service.getId()));
} Defensive patterns
Strategy: try-catch
Validate before calling
// Treat STs as single-use; never cache or retry with the same id
if (alreadyValidated(ticketId)) throw new IllegalStateException("ST already consumed"); Try / catch
try {
Assertion a = cas.validateServiceTicket(ticketId, service);
} catch (InvalidTicketException e) {
// ST unknown/expired/used: redirect to CAS login for a fresh ticket
response.sendRedirect(loginUrl + "?service=" + URLEncoder.encode(service.getId(), UTF_8));
} Prevention
- Validate each ST exactly once, immediately upon receipt
- Keep CAS ticket-registry backends identical across all cluster nodes
- Align ST TTL with application processing latency
- Handle clock skew on app servers to avoid premature expiry assumptions
When it happens
Trigger: Calling validateServiceTicket(serviceTicketId, service) with an id that was never issued, was already consumed/validated once, expired and got evicted, or lives in a different CAS node's registry backend.
Common situations: Application retries a used one-time service ticket; clock skew or long render delays let the ticket expire (default 10s TTL); clustered CAS nodes pointed at different ticket-registry stores; typo'd or truncated ticket id from a client.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- No authentication found for ticket
- Service ticket [ ] is not assigned a valid ticket granting…
- Could not grant service ticket
- Unknown tenant for service ticket
- Invalid token:
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/a4c4cf3af3deaf08.
Report an issue: GitHub.
Appendix: source
Thrown at core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java:162
doPublishEvent(new CasProxyTicketGrantedEvent(this, proxyGrantingTicket, addedProxyTicket, clientInfo));
return addedProxyTicket;
}))
.orElseThrow(UnauthorizedProxyingException::new);
}
@Audit(
action = AuditableActions.SERVICE_TICKET_VALIDATE,
actionResolverName = AuditActionResolvers.VALIDATE_SERVICE_TICKET_RESOLVER,
resourceResolverName = AuditResourceResolvers.VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER)
@Override
public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws Throwable {
if (!isTicketAuthenticityVerified(serviceTicketId)) {
LOGGER.info("Service ticket [{}] is not a valid ticket issued by CAS.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
val serviceTicket = configurationContext.getTicketRegistry().getTicket(serviceTicketId, ServiceTicket.class);
if (serviceTicket == null) {
LOGGER.warn("Service ticket [{}] does not exist.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
if (!(serviceTicket.getTicketGrantingTicket() instanceof TicketGrantingTicket) && !serviceTicket.isStateless()) {
LOGGER.warn("Service ticket [{}] is not assigned a valid ticket granting ticket", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);
}
try {
val selectedService = resolveServiceFromAuthenticationRequest(serviceTicket.getService());
val resolvedService = resolveServiceFromAuthenticationRequest(service);
LOGGER.debug("Resolved service [{}] from the authentication request with service [{}] linked to service ticket [{}]",
resolvedService, selectedService, serviceTicket.getId());
configurationContext.getLockRepository().execute(serviceTicket.getId(),
Unchecked.supplier(() -> {
if (serviceTicket.isExpired()) {
LOGGER.info("Service ticket [{}] has expired.", serviceTicketId);
throw new InvalidTicketException(serviceTicketId);View on GitHub (pinned to e7288fc434)