apereo/cas · warning

Security token linked to ticket

Error message

Security token linked to ticket [{}] has expired

What it means

The security-token ticket was found and valid, but the SecurityToken object it carries is null or itself expired, so the controller cannot return a usable token and logs this warning before returning null.

Solutions

  1. Increase the STS-issued token lifetime to exceed the security-token ticket TTL
  2. Decrease the ticket TTL to match token lifetime so they expire together
  3. Force the user to restart the federation flow to get a fresh token
  4. Check registry serialization if tokens come back null on clustered deployments

Example fix

// before: token lifetime shorter than ticket TTL
cas.authn.wsfed-idp.security-token-tickets.time-to-kill-in-seconds=600
// after: align TTLs
cas.authn.wsfed-idp.security-token-tickets.time-to-kill-in-seconds=300
Defensive patterns

Strategy: retry

Try / catch

if (token == null) { /* redirect user to restart the sign-in flow for a fresh token */ }

Prevention

When it happens

Trigger: getSecurityTokenFromRequest(): stt.getSecurityToken() is null, or stt.getSecurityToken().isExpired() is true after successful ticket lookup.

Common situations: Token TTL shorter than the ticket TTL, so the ticket outlives its token; STS issued token with very short lifetime; deserialization issue in a clustered registry returning a token object that cannot be reconstructed (null); long-paused browser flow.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-ws-idp/src/main/java/org/apereo/cas/ws/idp/web/BaseWSFederationRequestController.java:89

     * @return the security token from request
     */
    protected SecurityToken getSecurityTokenFromRequest(final HttpServletRequest request) {
        val cookieValue = configContext.getTicketGrantingTicketCookieGenerator().retrieveCookieValue(request);
        if (StringUtils.isNotBlank(cookieValue)) {
            val tgt = configContext.getTicketRegistry().getTicket(cookieValue, TicketGrantingTicket.class);
            if (tgt != null) {
                val sts = tgt.getDescendantTickets().stream()
                    .filter(t -> t.startsWith(SecurityTokenTicket.PREFIX))
                    .findFirst()
                    .orElse(null);
                if (StringUtils.isNotBlank(sts)) {
                    val stt = configContext.getTicketRegistry().getTicket(sts, SecurityTokenTicket.class);
                    if (stt == null || stt.isExpired()) {
                        LOGGER.warn("Security token ticket [{}] is not found or has expired", sts);
                        return null;
                    }
                    if (stt.getSecurityToken() == null || stt.getSecurityToken().isExpired()) {
                        LOGGER.warn("Security token linked to ticket [{}] has expired", sts);
                        return null;
                    }
                    return stt.getSecurityToken();
                }
            }
        }
        return null;
    }


    protected boolean shouldRenewAuthentication(final WSFederationRequest fedRequest,
                                                final HttpServletRequest request) {
        if (StringUtils.isBlank(fedRequest.wfresh()) || !NumberUtils.isCreatable(fedRequest.wfresh())) {
            return false;
        }
        val ttl = Long.parseLong(fedRequest.wfresh().trim());
        if (ttl == 0) {
            return false;

View on GitHub (pinned to e7288fc434)