lcobucci/jwt · error · ConstraintViolation
The token is expired
Error message
The token is expired
What it means
StrictValidAt's assertExpiration also verifies the token has not passed its 'exp' time using $token->isExpired($now). If the current time (plus configured leeway) is past exp, it throws 'The token is expired'. This is the canonical expired-JWT rejection in strict validation.
Solutions
- Refresh the token before use (refresh-token flow) and retry with the new token
- Increase leeway in StrictValidAt only if the issue is genuine minor clock skew, not real expiry
- Obtain a fresh token by re-authenticating if no refresh mechanism exists
- Check server clocks (NTP) if expiry appears prematurely relative to issuer expectations
Example fix
// before $validator->assert($oldToken, new StrictValidAt(new SystemClock::fromUTC())); // after (refresh first) $newToken = $auth->refresh($oldToken); $validator->assert($newToken, new StrictValidAt(new SystemClock::fromUTC()));
Defensive patterns
Strategy: try-catch
Validate before calling
if ($token->claims()->has('exp') && $token->isExpired((new DateTimeImmutable())->add($leeway))) {
// refresh before validating
} Type guard
null
Try / catch
try {
$validator->assert($token, new StrictValidAt($clock, $leeway));
} catch (ConstraintViolation $e) {
if ($e->getMessage() === 'The token is expired') { $token = $auth->refresh($refreshToken); }
} Prevention
- Refresh tokens proactively before their exp, not lazily after failure
- Set leeway to absorb acceptable clock skew (e.g. PT30S-PT5M)
- Never cache tokens longer than their lifetime
When it happens
Trigger: Validator::assert($token, new StrictValidAt(...)) where the token's exp claim is earlier than now->add(leeway) — i.e. the token's lifetime has elapsed.
Common situations: Cached/stored tokens used past their lifetime; long-running jobs holding a token across expiration; server clock differences between issuer and verifier beyond the configured leeway; users returning to an app with an old session token.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- This constraint was only usable until
- No constraint given.
- The claim " " is a registered claim, another constraint…
- You should pass a plain token
- The token does not have the claim
AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14).
Data as JSON: /api/errors/31d92277595ef265.
Report an issue: GitHub.
Appendix: source
Thrown at src/Validation/Constraint/StrictValidAt.php:57
throw ConstraintViolation::error('You should pass a plain token', $this);
}
$now = $this->clock->now();
$this->assertIssueTime($token, $now->add($this->leeway));
$this->assertMinimumTime($token, $now->add($this->leeway));
$this->assertExpiration($token, $now->sub($this->leeway));
}
/** @throws ConstraintViolation */
private function assertExpiration(UnencryptedToken $token, DateTimeInterface $now): void
{
if (! $token->claims()->has(Token\RegisteredClaims::EXPIRATION_TIME)) {
throw ConstraintViolation::error('"Expiration Time" claim missing', $this);
}
if ($token->isExpired($now)) {
throw ConstraintViolation::error('The token is expired', $this);
}
}
/** @throws ConstraintViolation */
private function assertMinimumTime(UnencryptedToken $token, DateTimeInterface $now): void
{
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
{View on GitHub (pinned to 375813049c)