lcobucci/jwt · error · ConstraintViolation

"Not Before" claim missing

Error message

"Not Before" claim missing

What it means

StrictValidAt requires the 'nbf' (Not Before) registered claim to be present and asserts it, because strict mode treats all three time claims (iat, nbf, exp) as mandatory. If the claim set lacks nbf, assertMinimumTime throws '"Not Before" claim missing' without evaluating token usability.

Solutions

  1. Fix the issuer to include ->canOnlyBeUsedAfter($now) (nbf) when building tokens
  2. Use a validator variant that does not require all claims if strictness is not required for your profile
  3. Add a claims()->has('nbf') pre-check at the trust boundary and surface a clearer rejection reason
  4. Coordinate the validator upgrade with reissuance of tokens that include nbf

Example fix

// before
$builder->issuedAt($now)->expiresAt($now->modify('+1 hour'));
// after
$builder->issuedAt($now)->canOnlyBeUsedAfter($now)->expiresAt($now->modify('+1 hour'));
Defensive patterns

Strategy: validation

Validate before calling

if (! $token->claims()->has('nbf')) {
    throw new InvalidArgumentException('Token must carry nbf claim for StrictValidAt');
}

Type guard

function hasNotBeforeClaim(UnencryptedToken $t): bool { return $t->claims()->has('nbf'); }

Try / catch

try {
    $validator->assert($token, new StrictValidAt($clock));
} catch (ConstraintViolation $e) {
    if (str_contains($e->getMessage(), '"Not Before" claim missing')) { /* reject; fix issuer */ }
}

Prevention

When it happens

Trigger: Validator::assert($token, new StrictValidAt(...)) on a token built without ->canOnlyBeUsedAfter() (or otherwise without the nbf claim).

Common situations: Minimal token issuers that only set iat/exp; third-party-issued JWTs that omit nbf; migrating from a lenient validator to StrictValidAt and older tokens lacking nbf suddenly rejected.

Related errors


AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14). Data as JSON: /api/errors/9a75fb7fcac96048. Report an issue: GitHub.

Appendix: source

Thrown at src/Validation/Constraint/StrictValidAt.php:65

    }

    /** @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
    {
        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)