{"record":{"id":"59754d37028e84d8","repo":"apereo/cas","slug":"proof-iat-is-in-the-future","errorCode":null,"errorMessage":"Proof iat is in the future","messagePattern":"Proof iat is in the future","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/proof/OidcVerifiableCredentialJwtProofValidator.java","lineNumber":107,"sourceCode":"        }\n        if (holderJwk instanceof RSAKey && !JWSAlgorithm.Family.RSA.contains(alg)) {\n            throw new IllegalArgumentException(\"Proof JWT algorithm does not match RSA holder key\");\n        }\n        if (holderJwk instanceof ECKey && !JWSAlgorithm.Family.EC.contains(alg)) {\n            throw new IllegalArgumentException(\"Proof JWT algorithm does not match EC holder key\");\n        }\n    }\n\n    protected void verifyFreshness(final SignedJWT signedJwt) throws ParseException {\n        val claims = signedJwt.getJWTClaimsSet();\n        val issuedAt = claims.getIssueTime();\n        if (issuedAt == null) {\n            throw new IllegalArgumentException(\"Proof JWT is missing iat\");\n        }\n        val now = Instant.now(Clock.systemUTC());\n        val iat = issuedAt.toInstant();\n        if (iat.isAfter(now.plusSeconds(SECONDS_IN_FUTURE))) {\n            throw new IllegalArgumentException(\"Proof iat is in the future\");\n        }\n        if (iat.isBefore(now.minus(Duration.ofMinutes(MINUTES_IN_PAST)))) {\n            throw new IllegalArgumentException(\"Proof JWT is too old\");\n        }\n    }\n}\n","sourceCodeStart":89,"sourceCodeEnd":114,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/proof/OidcVerifiableCredentialJwtProofValidator.java#L89-L114","documentation":"Thrown by OidcVerifiableCredentialJwtProofValidator.verifyFreshness when the proof JWT's iat claim is later than the current UTC clock plus a small allowed future skew (SECONDS_IN_FUTURE). A future-dated proof is treated as invalid because it cannot yet be trusted and may indicate clock skew or a forged proof.","triggerScenarios":"The client machine's clock is ahead of the CAS server clock by more than the allowed skew; the client intentionally sets iat in the future; timezone/timezone-conversion bugs producing a future timestamp in iat.","commonSituations":"Client and server in different environments with unsynced NTP; test harness generating timestamps in local time instead of UTC; container hosts with skewed clocks.","solutions":["Sync the client system clock (NTP) and generate iat from the current UTC time (Instant.now()).","Set iat to the actual issuance moment rather than a projected or future time.","If skew between trusted systems is unavoidable, increase SECONDS_IN_FUTURE tolerance in configuration/code.","Always build dates in UTC; avoid local-timezone Date construction in proof builders."],"exampleFix":"// before\n.issueTime(new Date(System.currentTimeMillis() + 300_000)) // 5 min in future\n// after\n.issueTime(Date.from(Instant.now(Clock.systemUTC())));","handlingStrategy":"validation","validationCode":"Date iat = claims.getIssueTime();\nif (iat != null && iat.toInstant().isAfter(Instant.now().plusSeconds(30))) {\n    throw new IllegalArgumentException(\"iat must not be in the future\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep client clocks NTP-synced with the CAS server.","Build timestamps with Instant.now(Clock.systemUTC()).","Avoid deliberate future-dating of iat in tests."],"tags":["jwt","iat","clock-skew","freshness","verifiable-credentials"],"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"}