apereo/cas · warning

Attribute query ticket

Error message

Attribute query ticket [{}] has either expired, or it is linked to a single sign-on session that is no longer valid and has now expired

What it means

This warning is logged when a SAML 2.0 Attribute Query request references an attribute query ticket that is absent from the ticket registry or has expired (a query ticket is also expired when its linked SSO session expires). The controller then throws InvalidTicketException and the Attribute Query profile request fails.

Solutions

  1. Verify SP attribute query timing is within the SamlAttributeQueryTicket TTL and the SSO session is still alive (check cas.authn.saml-idp.core attribute-query ticket expiration settings).
  2. Inspect the ticket registry (cas-management/actuator endpoints or registry backend) to confirm the ticket exists and has not been evicted.
  3. Ensure all CAS nodes share the same ticket registry when running a cluster.
  4. Increase cas.authn.saml-idp.ticket.attribute-query.time-to-kill-in-seconds and underlying SSO/TGT expiration if queries legitimately arrive late.
  5. Debug the id derivation: same NameID value and SP entity id must be used; confirm the SP is not resending modified NameIDs.

Example fix

// before (cas.properties)
cas.authn.saml-idp.ticket.attribute-query.time-to-kill-in-seconds=30
// after
cas.authn.saml-idp.ticket.attribute-query.time-to-kill-in-seconds=300
Defensive patterns

Strategy: validation

Validate before calling

var ticket = ticketRegistry.getTicket(id, SamlAttributeQueryTicket.class);
if (ticket == null || ticket.isExpired()) {
    throw new InvalidTicketException(id);
}

Try / catch

try {
    attributeQueryService.handleQuery(request);
} catch (InvalidTicketException e) {
    LOGGER.warn("Attribute query ticket expired: {}", e.getMessage());
    // respond with SAML error status
}

Prevention

When it happens

Trigger: A SAML SP sends an AttributeQuery whose NameID and entity ID hash to a SamlAttributeQueryTicket id, but the ticket was already consumed/expired via TTL/TGT-SSO expiry, ticket registry was flushed/evicted, or the SP sends a stale/cached query.

Common situations: Ticket registry cleaned by a shared cache (Redis/Hazelcast/Memcached) eviction policy; attribute queries sent long after SSO login; clock skew or aggressive SamlAttributeQueryTicket TTLs; SP caches ticket ids across restarts; clustering with a registry not shared between CAS nodes.

Related errors


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

Appendix: source

Thrown at support/cas-server-support-saml-idp-web/src/main/java/org/apereo/cas/support/saml/web/idp/profile/query/SamlIdPSaml2AttributeQueryProfileHandlerController.java:82

        }

        val ctx = decodeSoapRequest(request);
        val query = (AttributeQuery) ctx.getMessage();
        try {
            val issuer = Objects.requireNonNull(query).getIssuer().getValue();
            val registeredService = verifySamlRegisteredService(issuer, request);
            val adaptor = getSamlMetadataFacadeFor(registeredService, query);
            val facade = adaptor.orElseThrow(() -> UnauthorizedServiceException.denied("Cannot find metadata linked to %s".formatted(issuer)));
            verifyAuthenticationContextSignature(ctx, request, query, facade, registeredService);

            val nameIdValue = determineNameIdForQuery(query, registeredService, facade);
            val factory = (SamlAttributeQueryTicketFactory) getConfigurationContext().getTicketFactory()
                .get(SamlAttributeQueryTicket.class);
            val id = factory.createTicketIdFor(nameIdValue, facade.getEntityId());
            LOGGER.debug("Created ticket id for attribute query [{}]", id);
            val ticket = getConfigurationContext().getTicketRegistry().getTicket(id, SamlAttributeQueryTicket.class);
            if (ticket == null || ticket.isExpired()) {
                LOGGER.warn("Attribute query ticket [{}] has either expired, or it is linked to "
                            + "a single sign-on session that is no longer valid and has now expired", id);
                throw new InvalidTicketException(id);
            }
            val authentication = ticket.getAuthentication();

            val principal = resolvePrincipalForAttributeQuery(authentication, registeredService);
            val releasePolicyContext = RegisteredServiceAttributeReleasePolicyContext.builder()
                .registeredService(registeredService)
                .applicationContext(getConfigurationContext().getOpenSamlConfigBean().getApplicationContext())
                .service(ticket.getService())
                .principal(principal)
                .build();

            val principalAttributes = registeredService.getAttributeReleasePolicy().getConsentableAttributes(releasePolicyContext);
            LOGGER.debug("Initial consentable principal attributes are [{}]", principalAttributes);

            val authenticationAttributes = getConfigurationContext().getAuthenticationAttributeReleasePolicy()
                .getAuthenticationAttributesForRelease(authentication, null, Map.of(), registeredService);

View on GitHub (pinned to e7288fc434)