apereo/cas · warning
Ticket is issued before the allowed drift. Issued on
Error message
Ticket is issued before the allowed drift. Issued on [{}] while allowed drift is [{}] What it means
isValid() enforces a clock-skew tolerance (timeDrift, milliseconds) around the moment the token was retrieved. If the token's IssuedOn timestamp is earlier than (retrievedOn - timeDrift), the token is considered too old and the credential is rejected with this warning. This is replay/staleness protection for WS-Federation assertions.
Solutions
- Synchronize clocks via NTP on both the CAS server and the identity provider (this is the usual root cause).
- Increase cas.authn.wsfed[...].time-tolerance (timeDrift) to cover measured skew (e.g. 300000 ms = 5 min, ADFS default).
- Confirm users aren't replaying cached tokens; disable browser caching of the login POST or shorten token lifetime.
- Check for long-running delays between token issuance and CAS processing (proxies, offline validation).
Example fix
// before cas.authn.wsfed[0].identity-provider-metadata.time-tolerance=PT5S // after cas.authn.wsfed[0].identity-provider-metadata.time-tolerance=PT5M
Defensive patterns
Strategy: validation
Validate before calling
Instant now = Instant.now();
if (credential.getIssuedOn().isBefore(now.minusMillis(timeDrift))) {
throw new IllegalStateException("Token issued too far in the past: " + credential.getIssuedOn());
} Prevention
- Run NTP/chrony on every node that issues or consumes tokens.
- Set time tolerance to at least 5 minutes to absorb normal skew.
- Never cache and replay WS-Fed assertion responses across requests.
When it happens
Trigger: Calling isValid(expectedAudience, expectedIssuer, timeDrift) where getIssuedOn().isBefore(getRetrievedOn().minus(timeDrift, MILLIS)) — i.e. token creation time predates retrieval by more than the configured drift, typically because server clocks differ or cached/stale assertions are replayed.
Common situations: CAS server clock behind the ADFS IdP clock by more than the configured allowed drift; long network latency plus a tiny timeDrift setting; re-submitting an old token (browser back button, cached POST).
Related errors
- Ticket is too late because it's retrieved on
- Ticket is issued after the allowed drift. Retrieved on
- Token has expired: and is after
- Issuer [ ] is invalid since the expected issuer should be […
- Found multiple values for id attribute
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/dcbcb2a66bf8b4e8.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-wsfederation/src/main/java/org/apereo/cas/support/wsfederation/authentication/principal/WsFederationCredential.java:69
* Validates the credential.
*
* @param expectedAudience the audience that the token was issued to (CAS Server)
* @param expectedIssuer the issuer of the token (the IdP)
* @param timeDrift the amount of acceptable time drift
* @return true if the credentials are valid, otherwise false
*/
public boolean isValid(final String expectedAudience, final String expectedIssuer, final long timeDrift) {
if (!this.audience.equalsIgnoreCase(expectedAudience)) {
LOGGER.warn("Audience [{}] is invalid where the expected audience should be [{}]", this.audience, expectedAudience);
return false;
}
if (!this.issuer.equalsIgnoreCase(expectedIssuer)) {
LOGGER.warn("Issuer [{}] is invalid since the expected issuer should be [{}]", this.issuer, expectedIssuer);
return false;
}
val retrievedOnTimeDrift = getRetrievedOn().minus(timeDrift, ChronoUnit.MILLIS);
if (getIssuedOn().isBefore(retrievedOnTimeDrift)) {
LOGGER.warn("Ticket is issued before the allowed drift. Issued on [{}] while allowed drift is [{}]",
this.issuedOn, retrievedOnTimeDrift);
return false;
}
val retrievedOnTimeAfterDrift = getRetrievedOn().plus(timeDrift, ChronoUnit.MILLIS);
if (getIssuedOn().isAfter(retrievedOnTimeAfterDrift)) {
LOGGER.warn("Ticket is issued after the allowed drift. Retrieved on [{}] and issued on [{}] while allowed drift is [{}]",
getRetrievedOn(), getIssuedOn(), retrievedOnTimeAfterDrift);
return false;
}
if (getRetrievedOn().isAfter(this.notOnOrAfter)) {
LOGGER.warn("Ticket is too late because it's retrieved on [{}] which is after [{}].",
getRetrievedOn(), this.notOnOrAfter);
return false;
}
LOGGER.debug("WsFed Credential is validated for [{}] and [{}].", expectedAudience, expectedIssuer);
return true;
}
}View on GitHub (pinned to e7288fc434)