{"record":{"id":"a4c4cf3af3deaf08","repo":"apereo/cas","slug":"service-ticket-does-not-exist","errorCode":null,"errorMessage":"Service ticket [{}] does not exist.","messagePattern":"Service ticket \\[(.+?)\\] does not exist\\.","errorType":"exception","errorClass":"InvalidTicketException","httpStatus":null,"severity":"error","filePath":"core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java","lineNumber":162,"sourceCode":"                    doPublishEvent(new CasProxyTicketGrantedEvent(this, proxyGrantingTicket, addedProxyTicket, clientInfo));\n                    return addedProxyTicket;\n                }))\n            .orElseThrow(UnauthorizedProxyingException::new);\n    }\n\n    @Audit(\n        action = AuditableActions.SERVICE_TICKET_VALIDATE,\n        actionResolverName = AuditActionResolvers.VALIDATE_SERVICE_TICKET_RESOLVER,\n        resourceResolverName = AuditResourceResolvers.VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER)\n    @Override\n    public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws Throwable {\n        if (!isTicketAuthenticityVerified(serviceTicketId)) {\n            LOGGER.info(\"Service ticket [{}] is not a valid ticket issued by CAS.\", serviceTicketId);\n            throw new InvalidTicketException(serviceTicketId);\n        }\n        val serviceTicket = configurationContext.getTicketRegistry().getTicket(serviceTicketId, ServiceTicket.class);\n        if (serviceTicket == null) {\n            LOGGER.warn(\"Service ticket [{}] does not exist.\", serviceTicketId);\n            throw new InvalidTicketException(serviceTicketId);\n        }\n        if (!(serviceTicket.getTicketGrantingTicket() instanceof TicketGrantingTicket) && !serviceTicket.isStateless()) {\n            LOGGER.warn(\"Service ticket [{}] is not assigned a valid ticket granting ticket\", serviceTicketId);\n            throw new InvalidTicketException(serviceTicketId);\n        }\n\n        try {\n            val selectedService = resolveServiceFromAuthenticationRequest(serviceTicket.getService());\n            val resolvedService = resolveServiceFromAuthenticationRequest(service);\n            LOGGER.debug(\"Resolved service [{}] from the authentication request with service [{}] linked to service ticket [{}]\",\n                resolvedService, selectedService, serviceTicket.getId());\n\n            configurationContext.getLockRepository().execute(serviceTicket.getId(),\n                Unchecked.supplier(() -> {\n                    if (serviceTicket.isExpired()) {\n                        LOGGER.info(\"Service ticket [{}] has expired.\", serviceTicketId);\n                        throw new InvalidTicketException(serviceTicketId);","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/core/cas-server-core/src/main/java/org/apereo/cas/DefaultCentralAuthenticationService.java#L144-L180","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\ntry { st = cas.validateServiceTicket(ticketId, service); }\n// after (handle one-time-use ST)\ntry { st = cas.validateServiceTicket(ticketId, service); }\ncatch (InvalidTicketException e) {\n    // ST is single-use/expired: redirect user back to CAS for a fresh ticket\n    response.sendRedirect(loginUrl + \"?service=\" + encode(service.getId()));\n}","handlingStrategy":"try-catch","validationCode":"// Treat STs as single-use; never cache or retry with the same id\nif (alreadyValidated(ticketId)) throw new IllegalStateException(\"ST already consumed\");","typeGuard":null,"tryCatchPattern":"try {\n    Assertion a = cas.validateServiceTicket(ticketId, service);\n} catch (InvalidTicketException e) {\n    // ST unknown/expired/used: redirect to CAS login for a fresh ticket\n    response.sendRedirect(loginUrl + \"?service=\" + URLEncoder.encode(service.getId(), UTF_8));\n}","preventionTips":["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"],"tags":["tickets","service-ticket","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"}