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

  1. Refresh the token before use (refresh-token flow) and retry with the new token
  2. Increase leeway in StrictValidAt only if the issue is genuine minor clock skew, not real expiry
  3. Obtain a fresh token by re-authenticating if no refresh mechanism exists
  4. 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

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

Related errors


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)