apereo/cas · warning

Could not grant service ticket

Error message

Could not grant service ticket [{}]. Routing to [{}]

What it means

This is a WARN log emitted by the CAS GenerateServiceTicketAction when a service ticket cannot be granted from the presented ticket-granting ticket (TGT). After the failure, the webflow is routed to the 'authenticationFailure' transition, so the login flow terminates with an authentication failure event rather than a service ticket.

Solutions

  1. Check the accompanying exception message (the {} placeholder shows e.getMessage()) to identify the root cause (expired TGT vs registry error).
  2. Verify the ticket registry (cas.ticket.registry.*) is up, shared across CAS nodes, and not dropping tickets prematurely.
  3. Confirm the service parameter matches a registered service in the services registry and that the TGT was issued for the same service context.
  4. Increase ticket-granting-ticket timeout (cas.ticket.tgt.time-to-kill-in-seconds) if tickets expire mid-flow.

Example fix

// before: TGT expires before ST request
cas.ticket.tgt.time-to-kill-in-seconds=10
// after
cas.ticket.tgt.time-to-kill-in-seconds=28800
Defensive patterns

Strategy: try-catch

Validate before calling

// before triggering the ST grant
val tgt = ticketRegistrySupport.getTicketRegistry().getTicket(tgtId, TicketGrantingTicket.class);
if (tgt == null || tgt.isExpired()) { return error("expired"); }
if (servicesManager.findServiceBy(service) == null) { return error("unauthorized-service"); }

Type guard

function isLiveTgt(t: Ticket | null): t is TicketGrantingTicket {
  return t instanceof TicketGrantingTicket && !t.isExpired();
}

Try / catch

try {
  return grantServiceTicket(authnResult, service, context);
} catch (TicketException | AbstractTicketException e) {
  LOGGER.warn("Could not grant service ticket: [{}]", e.getMessage());
  return newEvent(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE, e);
}

Prevention

When it happens

Trigger: doExecuteInternal catches a throwable raised inside grantServiceTicket (e.g. TicketException from ticketRegistry.addTicket, expired/invalid TGT, service not authorized for the TGT) and logs this message before returning the authentication-failure event.

Common situations: TGT expired or evicted from the ticket registry (short ticket timeout, shared registry like Redis/JDBC flushed); service ID mismatch between the service presented and the one bound to the TGT; ticket registry connectivity problems; service not found/authorized in the services registry.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/45da8eebab6ab534. Report an issue: GitHub.

Appendix: source

Thrown at support/cas-server-support-actions-core/src/main/java/org/apereo/cas/web/flow/GenerateServiceTicketAction.java:116

            val credentials = casWebflowCredentialProvider.extract(context);
            val builder = authenticationSystemSupport.establishAuthenticationContextFromInitial(authentication,
                credentials.toArray(Credential.EMPTY_CREDENTIALS_ARRAY));
            val authenticationResult = builder.build(service);

            LOGGER.trace("Built the final authentication result [{}] to grant service ticket to [{}]", authenticationResult, service);
            grantServiceTicket(authenticationResult, service, context);
            return success();

        } catch (final Throwable e) {
            if (e instanceof InvalidTicketException) {
                LOGGER.debug("CAS has determined ticket-granting ticket [{}] is invalid and must be destroyed", ticketGrantingTicket);
                ticketRegistrySupport.getTicketRegistry().deleteTicket(ticketGrantingTicket);
            }
            if (isGatewayPresent(context)) {
                LOGGER.debug("Request indicates that it is gateway. Routing result to [{}] state", CasWebflowConstants.TRANSITION_ID_GATEWAY);
                return result(CasWebflowConstants.TRANSITION_ID_GATEWAY);
            }
            LOGGER.warn("Could not grant service ticket [{}]. Routing to [{}]", e.getMessage(), CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE);
            return newEvent(CasWebflowConstants.TRANSITION_ID_AUTHENTICATION_FAILURE, e);
        }
    }

    private void grantServiceTicket(final AuthenticationResult authenticationResult,
                                    final Service service,
                                    final RequestContext requestContext) {
        serviceTicketAuthorities
            .stream()
            .sorted(AnnotationAwareOrderComparator.INSTANCE)
            .filter(auth -> auth.supports(authenticationResult, service))
            .findFirst()
            .ifPresent(Unchecked.consumer(auth -> {
                if (auth.shouldGenerate(authenticationResult, service)) {
                    FunctionUtils.doUnchecked(_ -> {
                        val ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(requestContext);
                        val serviceTicketId = centralAuthenticationService.grantServiceTicket(ticketGrantingTicket, service, authenticationResult);
                        WebUtils.putServiceTicketInRequestScope(requestContext, serviceTicketId);

View on GitHub (pinned to e7288fc434)