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

  1. Validate the ST exactly once and immediately; obtain a new one by re-authenticating the user if needed
  2. Increase cas.ticket.st.time-to-kill-in-seconds if clients legitimately need longer validation windows
  3. Verify all CAS nodes share the same ticket registry backend (e.g. same Redis/Hazelcast/JDBC store)
  4. 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

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


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)