apereo/cas · error · IllegalArgumentException
Proof iat is in the future
Error message
Proof iat is in the future
What it means
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.
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.
Example fix
// before .issueTime(new Date(System.currentTimeMillis() + 300_000)) // 5 min in future // after .issueTime(Date.from(Instant.now(Clock.systemUTC())));
Defensive patterns
Strategy: validation
Validate before calling
Date iat = claims.getIssueTime();
if (iat != null && iat.toInstant().isAfter(Instant.now().plusSeconds(30))) {
throw new IllegalArgumentException("iat must not be in the future");
} Prevention
- Keep client clocks NTP-synced with the CAS server.
- Build timestamps with Instant.now(Clock.systemUTC()).
- Avoid deliberate future-dating of iat in tests.
When it happens
Trigger: 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.
Common situations: Client and server in different environments with unsynced NTP; test harness generating timestamps in local time instead of UTC; container hosts with skewed clocks.
Related errors
- Proof JWT is missing iat
- Token has expired: and is after
- Token cannot be used before
- 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/59754d37028e84d8.
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:107
}
if (holderJwk instanceof RSAKey && !JWSAlgorithm.Family.RSA.contains(alg)) {
throw new IllegalArgumentException("Proof JWT algorithm does not match RSA holder key");
}
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)