apereo/cas · error · IllegalArgumentException
Proof JWT is too old
Error message
Proof JWT is too old
What it means
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.
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
Example fix
// before String proofJwt = buildProofJwt(); // iat set long ago submitCredentialRequest(proofJwt); // after String proofJwt = buildProofJwt(Instant.now(Clock.systemUTC())); // fresh iat immediately before submission submitCredentialRequest(proofJwt);
Defensive patterns
Strategy: validation
Validate before calling
Instant iat = issuedAt.toInstant(); Instant now = Instant.now(Clock.systemUTC()); boolean fresh = !iat.isAfter(now.plusSeconds(300)) && iat.isAfter(now.minus(Duration.ofMinutes(5)));
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Token has expired: and is after
- Token cannot be used before
- Unable to verify JWT assertion with any of the configured…
- Proof nonce is invalid or missing
- Proof JWT signature validation failed
AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08).
Data as JSON: /api/errors/e34de56c9d0f79dc.
Report an issue: GitHub.
Appendix: source
Thrown at support/cas-server-support-oidc-vc/src/main/java/org/apereo/cas/oidc/vc/issuer/proof/OidcVerifiableCredentialJwtProofValidator.java:110
}
if (holderJwk instanceof ECKey && !JWSAlgorithm.Family.EC.contains(alg)) {
throw new IllegalArgumentException("Proof JWT algorithm does not match EC holder key");
}
}
protected void verifyFreshness(final SignedJWT signedJwt) throws ParseException {
val claims = signedJwt.getJWTClaimsSet();
val issuedAt = claims.getIssueTime();
if (issuedAt == null) {
throw new IllegalArgumentException("Proof JWT is missing iat");
}
val now = Instant.now(Clock.systemUTC());
val iat = issuedAt.toInstant();
if (iat.isAfter(now.plusSeconds(SECONDS_IN_FUTURE))) {
throw new IllegalArgumentException("Proof iat is in the future");
}
if (iat.isBefore(now.minus(Duration.ofMinutes(MINUTES_IN_PAST)))) {
throw new IllegalArgumentException("Proof JWT is too old");
}
}
}
View on GitHub (pinned to e7288fc434)