apereo/cas · error · InvalidTicketException
InvalidTicketException
Error message
InvalidTicketException
What it means
CAS REST protocol endpoint throws InvalidTicketException when the supplied TGT id cannot be resolved to an authentication in the ticket registry. The endpoint looks up the TicketGrantingTicket's authentication via ticketRegistrySupport; a null result means the TGT is unknown, expired, or already consumed. It is thrown before any service ticket is created.
Solutions
- Re-acquire a fresh TGT first (POST to /v1/tickets with username/password) and retry with the new tgtId.
- Increase ticket expiration policy (cas.ticket.tgt.timeout) or configure a persistent/replicated ticket registry (Redis/JDBC/Hazelcast) for multi-node deployments.
- Verify the exact tgtId value is passed unmodified and correctly URL-encoded in the request path.
- Check ticketRegistrySupport's underlying registry health and that both CAS nodes point at the same registry.
Example fix
// before curl -X POST 'https://cas/v1/tickets/TGT-1-abc/service?service=https://app' // after curl -u casuser:password -X POST 'https://cas/v1/tickets' -d 'username=casuser&password=pass' # get fresh TGT curl -X POST 'https://cas/v1/tickets/TGT-2-newid/service' -d 'service=https://app'
Defensive patterns
Strategy: validation
Validate before calling
// Before calling the endpoint, confirm the TGT exists (or just re-acquire):
boolean tgtLikelyValid = tgtId != null && tgtId.startsWith("TGT-")
&& System.currentTimeMillis() - lastTgtFetch < tgtMaxIdleMillis;
if (!tgtLikelyValid) { tgtId = fetchNewTicketGrantingTicket(user, pass); } Try / catch
try { createServiceTicket(tgtId, service); }
catch (InvalidTicketException | RestHttpException e) {
String freshTgt = fetchNewTicketGrantingTicket(user, pass);
createServiceTicket(freshTgt, service);
} Prevention
- Always re-acquire a TGT upon any invalid-ticket response instead of retrying with the same id.
- Use a persistent/replicated ticket registry when running multiple CAS nodes.
- Track TGT idle timeout client-side and refresh proactively.
- URL-encode the TGT id when placing it in the request path.
When it happens
Trigger: POST/GET to /v1/tickets/{tgtId} where tgtId does not exist in the ticket registry, the TGT has expired (default 8h idle timeout), the registry was flushed/restarted (in-memory registry), or the tgtId string is malformed/URL-encoded incorrectly so the lookup misses.
Common situations: Client waits too long between TGT creation and service ticket requests; CAS restarted with an in-memory ticket registry invalidating all issued TGTs; client sends TGC cookie value from a different CAS node without replicated registry; user forgets to URL-encode the TGT id (TGT-...-cassuffix containing special chars).
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 credentials are provided or extracted to authenticate…
- Unable to authenticate request to register service
- Unable to establish authentication using provided…
- warn(logger, getMessage(throwable), throwable)
- Found removable encoded ticket
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/1168e7743a599467.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-rest-core/src/main/java/org/apereo/cas/support/rest/resources/ServiceTicketResource.java:96
MediaType.APPLICATION_FORM_URLENCODED_VALUE,
MediaType.APPLICATION_JSON_VALUE,
MediaType.TEXT_HTML_VALUE,
MediaType.TEXT_PLAIN_VALUE
})
@Operation(summary = "Create service ticket",
parameters = {
@Parameter(name = "tgtId", required = true, in = ParameterIn.PATH, description = "Ticket-granting ticket id"),
@Parameter(name = "requestBody", required = false, description = "Request body containing credentials")
})
public ResponseEntity<String> createServiceTicket(
final HttpServletRequest httpServletRequest,
@RequestBody(required = false)
final MultiValueMap<String, String> requestBody,
@PathVariable final String tgtId) {
try {
val authn = ticketRegistrySupport.getAuthenticationFrom(StringEscapeUtils.escapeHtml4(tgtId));
if (authn == null) {
throw new InvalidTicketException(tgtId);
}
val service = Objects.requireNonNull(argumentExtractor.extractService(httpServletRequest),
"Target service/application is unspecified or unrecognized in the request");
if (BooleanUtils.toBoolean(httpServletRequest.getParameter(CasProtocolConstants.PARAMETER_RENEW))) {
val credential = credentialFactory.fromRequest(httpServletRequest, requestBody);
if (credential == null || credential.isEmpty()) {
throw new BadRestRequestException("No credentials are provided or extracted to authenticate the REST request");
}
val authenticationResult = authenticationSystemSupport.finalizeAuthenticationTransaction(service, credential);
return serviceTicketResourceEntityResponseFactory.build(tgtId, service, Objects.requireNonNull(authenticationResult));
}
val builder = authenticationSystemSupport.getAuthenticationResultBuilderFactory().newBuilder();
val authenticationResult = builder.collect(authn).build(service);
return serviceTicketResourceEntityResponseFactory.build(tgtId, service, Objects.requireNonNull(authenticationResult));
} catch (final InvalidTicketException e) {
return new ResponseEntity<>(StringEscapeUtils.escapeHtml4(tgtId) + " could not be found or is considered invalid", HttpStatus.NOT_FOUND);
} catch (final AuthenticationException e) {
return RestResourceUtils.createResponseEntityForAuthnFailure(e, httpServletRequest, applicationContext);View on GitHub (pinned to e7288fc434)