lcobucci/jwt · error · ConstraintViolation
The token was issued in the future
Error message
The token was issued in the future
What it means
This error comes from lcobucci/jwt's StrictValidAt validation constraint. When a token is validated with the constraint (via Validator::assert), it checks the 'iat' (Issued At) claim against the current clock using Token::hasBeenIssuedBefore(). If the iat timestamp is later than 'now' — even by one second — the library rejects the token because it could not have been issued yet, which usually means clock skew between the issuer and validator or a bad clock on the machine.
Solutions
- Synchronize clocks on both issuer and validator machines with NTP (e.g. enable timed/chrony, 'sudo timedatectl set-ntp true') so iat is never in the future relative to the validator.
- If small skew is unavoidable, allow leeway: pass a DateInterval to the constraint, e.g. new StrictValidAt(SystemClock::fromUTC(), new DateInterval('PT30S')), which tolerates tokens issued up to 30s in the future.
- Fix the token generation code that sets iat — ensure it uses the current time in seconds (new DateTimeImmutable('@' . time()) or simply new DateTimeImmutable('now')) and not milliseconds or a future date.
- Catch RequiredConstraintsViolated and reject/refresh the token instead of crashing, logging the iat vs now values to diagnose persistent skew.
Example fix
// before
$validator->assert($token, new StrictValidAt($clock)); // throws when iat is a few seconds ahead
// after — tolerate up to 30 seconds of clock skew
$validator->assert(
$token,
new StrictValidAt(SystemClock::fromUTC(), new DateInterval('PT30S'))
); Defensive patterns
Strategy: try-catch
Validate before calling
// before asserting, sanity-check the iat claim yourself
$claims = $token->claims();
$leeway = 30;
if ($claims->has('iat')) {
$iat = $claims->get('iat');
$now = new DateTimeImmutable('now', new DateTimeZone('UTC'));
if ($iat->getTimestamp() > $now->getTimestamp() + $leeway) {
// token issued in the future: fix clocks or reject early
}
} Type guard
function hasFutureIat(Lcobucci\JWT\Token $token, int $leeway = 30): bool
{
if (!$token->claims()->has('iat')) {
return false;
}
$iat = $token->claims()->get('iat');
return $iat->getTimestamp() > (new DateTimeImmutable())->getTimestamp() + $leeway;
} Try / catch
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
try {
$validator->assert($token, new StrictValidAt($clock, new DateInterval('PT30S')));
} catch (RequiredConstraintsViolated $e) {
foreach ($e->getViolations() as $v) {
if ($v->getMessage() === 'The token was issued in the future') {
// handle clock skew: refresh token or resync NTP
}
}
} Prevention
- Run NTP (chrony/timed) on every server that issues or validates tokens.
- Always configure a leeway DateInterval (15–60s) in StrictValidAt/ValidAt.
- Never hand-build iat claims; use the library's Builder->issuedAt(new DateTimeImmutable('now')) and keep timestamps in seconds, not milliseconds.
- In tests, generate tokens relative to a frozen clock (setTestNow) consistent with the validation clock.
When it happens
Trigger: Running Validator->assert($token, new StrictValidAt($clock)) where the token's iat claim is a Unix timestamp greater than the clock's current time. Typical concrete calls: validating a JWT received from another server/service whose clock is a few seconds or minutes ahead, or a token minted with a hand-built iat set incorrectly in the future (e.g. using milliseconds instead of seconds, or a wrong timezone conversion).
Common situations: Distributed systems with unsynchronized NTP clocks between the token issuer (auth server) and validator (API server); local development where the machine clock drifted; test fixtures that hardcode iat timestamps in the future; microservices where the token was just generated and iat equals the remote clock's 'now' while the local clock lags behind.
Related errors
- The token cannot be used yet
- Required constraints violated
- No constraint given.
- Error while decoding from Base64Url, invalid base64…
- Builder#withClaim() is meant to be used for non-registered…
AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14).
Data as JSON: /api/errors/c1f7bed65ae25840.
Report an issue: GitHub.
Appendix: source
Thrown at src/Validation/Constraint/StrictValidAt.php:81
{
if (! $token->claims()->has(Token\RegisteredClaims::NOT_BEFORE)) {
throw ConstraintViolation::error('"Not Before" claim missing', $this);
}
if (! $token->isMinimumTimeBefore($now)) {
throw ConstraintViolation::error('The token cannot be used yet', $this);
}
}
/** @throws ConstraintViolation */
private function assertIssueTime(UnencryptedToken $token, DateTimeInterface $now): void
{
if (! $token->claims()->has(Token\RegisteredClaims::ISSUED_AT)) {
throw ConstraintViolation::error('"Issued At" claim missing', $this);
}
if (! $token->hasBeenIssuedBefore($now)) {
throw ConstraintViolation::error('The token was issued in the future', $this);
}
}
}
View on GitHub (pinned to 375813049c)