{"record":{"id":"c917852527355a34","repo":"apereo/cas","slug":"ticket-is-issued-after-the-allowed-drift-retrieve","errorCode":null,"errorMessage":"Ticket is issued after the allowed drift. Retrieved on [{}] and issued on [{}] while allowed drift is [{}]","messagePattern":"Ticket is issued after the allowed drift\\. Retrieved on \\[(.+?)\\] and 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":75,"sourceCode":"     */\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}\n","sourceCodeStart":57,"sourceCodeEnd":88,"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#L57-L88","documentation":"isValid() also rejects tokens whose IssuedOn timestamp is in the future relative to the retrieval time plus the allowed drift. If getIssuedOn().isAfter(getRetrievedOn().plus(timeDrift, MILLIS)), the token claims to have been issued later than plausible (clock skew in the other direction), so it is rejected with this warning.","triggerScenarios":"Calling isValid(expectedAudience, expectedIssuer, timeDrift) where the token's IssuedOn exceeds retrievedOn + timeDrift — the IdP clock is ahead of the CAS server clock, or a forged/fabricated assertion carries a future issuance time.","commonSituations":"ADFS host clock running ahead of CAS server time; VM clocks drifting without NTP; new token tests failing immediately after clock changes on either host.","solutions":["Enable NTP/chrony on both CAS and the IdP and let clocks converge.","Increase the configured time tolerance (timeDrift) so modest forward skew is tolerated.","Verify token integrity — a future IssuedOn can indicate tampering; check signature validation settings.","If skew is expected and benign, re-test after confirming both servers report the same UTC time."],"exampleFix":"// before\nval timeDrift = 60000L; // 1 minute, too small for skewed VMs\n// after\nval timeDrift = 300000L; // 5 minutes, ADFS-style tolerance","handlingStrategy":"validation","validationCode":"Instant now = Instant.now();\nif (credential.getIssuedOn().isAfter(now.plusMillis(timeDrift))) {\n    throw new IllegalStateException(\"Token issuedOn is in the future: \" + credential.getIssuedOn());\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Monitor clock drift between CAS and the IdP with NTP alerting.","Keep signature validation enabled so fabricated future-dated tokens are rejected upstream.","Re-test federation flows after any host clock resync or VM migration."],"tags":["wsfederation","clock-skew","token-validation","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"}