{"record":{"id":"dcbcb2a66bf8b4e8","repo":"apereo/cas","slug":"ticket-is-issued-before-the-allowed-drift-issued","errorCode":null,"errorMessage":"Ticket is issued before the allowed drift. Issued on [{}] while allowed drift is [{}]","messagePattern":"Ticket is issued before the allowed drift\\. Issued on \\[(.+?)\\] while allowed drift is \\[(.+?)\\]","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-wsfederation/src/main/java/org/apereo/cas/support/wsfederation/authentication/principal/WsFederationCredential.java","lineNumber":69,"sourceCode":"     * Validates the credential.\n     *\n     * @param expectedAudience the audience that the token was issued to (CAS Server)\n     * @param expectedIssuer   the issuer of the token (the IdP)\n     * @param timeDrift        the amount of acceptable time drift\n     * @return true if the credentials are valid, otherwise false\n     */\n    public boolean isValid(final String expectedAudience, final String expectedIssuer, final long timeDrift) {\n        if (!this.audience.equalsIgnoreCase(expectedAudience)) {\n            LOGGER.warn(\"Audience [{}] is invalid where the expected audience should be [{}]\", this.audience, expectedAudience);\n            return false;\n        }\n        if (!this.issuer.equalsIgnoreCase(expectedIssuer)) {\n            LOGGER.warn(\"Issuer [{}] is invalid since the expected issuer should be [{}]\", this.issuer, expectedIssuer);\n            return false;\n        }\n        val retrievedOnTimeDrift = getRetrievedOn().minus(timeDrift, ChronoUnit.MILLIS);\n        if (getIssuedOn().isBefore(retrievedOnTimeDrift)) {\n            LOGGER.warn(\"Ticket is issued before the allowed drift. Issued on [{}] while allowed drift is [{}]\",\n                this.issuedOn, retrievedOnTimeDrift);\n            return false;\n        }\n        val retrievedOnTimeAfterDrift = getRetrievedOn().plus(timeDrift, ChronoUnit.MILLIS);\n        if (getIssuedOn().isAfter(retrievedOnTimeAfterDrift)) {\n            LOGGER.warn(\"Ticket is issued after the allowed drift. Retrieved on [{}] and issued on [{}] while allowed drift is [{}]\",\n                getRetrievedOn(), getIssuedOn(), retrievedOnTimeAfterDrift);\n            return false;\n        }\n        if (getRetrievedOn().isAfter(this.notOnOrAfter)) {\n            LOGGER.warn(\"Ticket is too late because it's retrieved on [{}] which is after [{}].\",\n                getRetrievedOn(), this.notOnOrAfter);\n            return false;\n        }\n        LOGGER.debug(\"WsFed Credential is validated for [{}] and [{}].\", expectedAudience, expectedIssuer);\n        return true;\n    }\n}","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-wsfederation/src/main/java/org/apereo/cas/support/wsfederation/authentication/principal/WsFederationCredential.java#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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)."],"exampleFix":"// before\ncas.authn.wsfed[0].identity-provider-metadata.time-tolerance=PT5S\n// after\ncas.authn.wsfed[0].identity-provider-metadata.time-tolerance=PT5M","handlingStrategy":"validation","validationCode":"Instant now = Instant.now();\nif (credential.getIssuedOn().isBefore(now.minusMillis(timeDrift))) {\n    throw new IllegalStateException(\"Token issued too far in the past: \" + credential.getIssuedOn());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["wsfederation","clock-skew","token-expired","sso"],"backgroundTag":"jwt-token-expired","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}