{"record":{"id":"54dee38f10a2d983","repo":"lcobucci/jwt","slug":"expiration-time-claim-missing","errorCode":null,"errorMessage":"\"Expiration Time\" claim missing","messagePattern":"\"Expiration Time\" claim missing","errorType":"validation","errorClass":"ConstraintViolation","httpStatus":null,"severity":"error","filePath":"src/Validation/Constraint/StrictValidAt.php","lineNumber":53,"sourceCode":"\n    public function assert(Token $token): void\n    {\n        if (! $token instanceof UnencryptedToken) {\n            throw ConstraintViolation::error('You should pass a plain token', $this);\n        }\n\n        $now = $this->clock->now();\n\n        $this->assertIssueTime($token, $now->add($this->leeway));\n        $this->assertMinimumTime($token, $now->add($this->leeway));\n        $this->assertExpiration($token, $now->sub($this->leeway));\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertExpiration(UnencryptedToken $token, DateTimeInterface $now): void\n    {\n        if (! $token->claims()->has(Token\\RegisteredClaims::EXPIRATION_TIME)) {\n            throw ConstraintViolation::error('\"Expiration Time\" claim missing', $this);\n        }\n\n        if ($token->isExpired($now)) {\n            throw ConstraintViolation::error('The token is expired', $this);\n        }\n    }\n\n    /** @throws ConstraintViolation */\n    private function assertMinimumTime(UnencryptedToken $token, DateTimeInterface $now): void\n    {\n        if (! $token->claims()->has(Token\\RegisteredClaims::NOT_BEFORE)) {\n            throw ConstraintViolation::error('\"Not Before\" claim missing', $this);\n        }\n\n        if (! $token->isMinimumTimeBefore($now)) {\n            throw ConstraintViolation::error('The token cannot be used yet', $this);\n        }\n    }","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/lcobucci/jwt/blob/375813049c24c7111bda8b6884c57b071ceb2fe7/src/Validation/Constraint/StrictValidAt.php#L35-L71","documentation":"StrictValidAt enforces that exp, nbf, and iat are ALL present, unlike lenient validators that skip missing claims. If the token has no 'exp' (Expiration Time) registered claim, assertExpiration throws '\"Expiration Time\" claim missing' before even checking expiry. This guards against never-expiring tokens being accepted.","triggerScenarios":"Validating with StrictValidAt a token whose claim set lacks the RegisteredClaims::EXPIRATION_TIME ('exp') — e.g. tokens issued by builder code that only called withClaim('iat', ...) and never expiresAt().","commonSituations":"Issuer library configured without mandatory exp; handcrafted or third-party JWTs that omit exp; token builder refactored and the expiresAt() call dropped; accepting tokens from an external service that does not set exp.","solutions":["Fix the issuer to always call ->expiresAt(new DateTimeImmutable(...)) (or equivalent) when building tokens","If strictness is undesired, use a non-strict constraint (e.g. ValidAt with optional claims behavior / LooseValidAt if available) that tolerates missing exp","Add pre-validation check: $token->claims()->has('exp') and reject such tokens at the boundary with a clearer error","Contact the token provider to require exp per your token profile (RFC 8725 recommends exp)"],"exampleFix":"// before\n$builder->issuedBy('me')->withClaim('iat', $now);\n// after\n$builder->issuedBy('me')->issuedAt($now)->expiresAt($now->modify('+1 hour'));","handlingStrategy":"try-catch","validationCode":"if (! $token->claims()->has('exp')) {\n    throw new InvalidArgumentException('Token must carry exp claim for StrictValidAt');\n}","typeGuard":"function hasExpirationClaim(UnencryptedToken $t): bool { return $t->claims()->has('exp'); }","tryCatchPattern":"try {\n    $validator->assert($token, new StrictValidAt($clock));\n} catch (ConstraintViolation $e) {\n    if (str_contains($e->getMessage(), 'claim missing')) { /* reject token; require exp */ }\n}","preventionTips":["Enforce exp at issuance in the token builder; make it mandatory in your issuer wrapper","Reject exp-less tokens at ingestion from third parties","Document your token profile (RFC 8725) with exp as required"],"tags":["jwt","missing-claim","validation","strict-mode"],"backgroundTag":"missing-required-claim","analyzedSha":"375813049c24c7111bda8b6884c57b071ceb2fe7","analyzedAt":"2026-09-14T11:12:28.004Z","contentChangedAt":"2026-09-14T11:12:28.004Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}