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

  1. Sync the client system clock (NTP) and generate iat from the current UTC time (Instant.now()).
  2. Set iat to the actual issuance moment rather than a projected or future time.
  3. If skew between trusted systems is unavoidable, increase SECONDS_IN_FUTURE tolerance in configuration/code.
  4. 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

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


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)