{"record":{"id":"e34de56c9d0f79dc","repo":"apereo/cas","slug":"proof-jwt-is-too-old","errorCode":null,"errorMessage":"Proof JWT is too old","messagePattern":"Proof JWT is too old","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":110,"sourceCode":"        }\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":92,"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#L92-L114","documentation":"OidcVerifiableCredentialJwtProofValidator.verifyFreshness rejects a Proof JWT whose 'iat' claim is older than the allowed past window (MINUTES_IN_PAST minutes before current UTC time). CAS enforces freshness so a captured proof cannot be replayed later during credential issuance or presentation.","triggerScenarios":"Calling validate() on a verifiable-credential proof JWT whose 'iat' Instant is before now minus MINUTES_IN_PAST, i.e. the client built the proof more than the configured minutes ago, or the client clock is behind the server clock.","commonSituations":"Client devices with skewed clocks (NTP drift), proofs generated at flow start but submitted after long user interaction, or overly tight server-side freshness window configuration.","solutions":["Regenerate the proof JWT right before submitting the request so 'iat' is current","Check client clock sync (NTP) on the device producing the proof JWT","Increase the server freshness window (cas.authn.oidc.vc proof past-minutes setting) if network/UX latency legitimately exceeds it"],"exampleFix":"// before\nString proofJwt = buildProofJwt(); // iat set long ago\nsubmitCredentialRequest(proofJwt);\n// after\nString proofJwt = buildProofJwt(Instant.now(Clock.systemUTC())); // fresh iat immediately before submission\nsubmitCredentialRequest(proofJwt);","handlingStrategy":"validation","validationCode":"Instant iat = issuedAt.toInstant();\nInstant now = Instant.now(Clock.systemUTC());\nboolean fresh = !iat.isAfter(now.plusSeconds(300)) && iat.isAfter(now.minus(Duration.ofMinutes(5)));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Generate proof JWTs immediately before the request, not at flow start","Keep client clocks NTP-synced","Track the configured past/future window server-side and mirror it in clients"],"tags":["jwt","oidc","vc","clock-skew"],"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"}