require-issued-at
This rule ensures tokens have the iat claim for freshness validation
Require iat (issued at) claim for token freshness validation
Severity: 🟡 Medium
CWE: CWE-294
Rule Details
This rule ensures tokens have the iat claim for freshness validation. Most libraries add iat automatically unless explicitly disabled.
Examples
❌ Incorrect
// A noTimestamp chosen at runtime may drop iat on some path
jwt.sign(payload, secret, { noTimestamp: process.env.LEGACY === '1' });
// jose does NOT add iat unless asked
await new SignJWT({ sub })
.setProtectedHeader({ alg: 'ES256' })
.setExpirationTime('1h')
.sign(key);✅ Correct
// Default - iat is added automatically
jwt.sign(payload, secret);
// Explicit iat in payload
jwt.sign({ sub: 'user', iat: Math.floor(Date.now() / 1000) }, secret);
// noTimestamp: false keeps iat (it is the default, made explicit)
jwt.sign(payload, secret, { noTimestamp: false });
// noTimestamp: true is reported by no-timestamp-manipulation, not here —
// one defect, one finding
jwt.sign(payload, secret, { noTimestamp: true });
// jose with setIssuedAt()
await new SignJWT({ sub })
.setProtectedHeader({ alg: 'ES256' })
.setIssuedAt()
.sign(key);Options
| Option | Type | Default | Description |
|---|---|---|---|
trustedSanitizers | string[] | [] | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
trustedAnnotations | string[] | [] | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
strictMode | boolean | false | No effect — accepted so existing configs stay valid; the rule does not read it yet. |
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Options from Variables, Casts and Spreads
What is read: an inline object, a same-file const, an as / satisfies cast, and a spread of such a const. Options from a parameter, an import or a call cannot be seen.
// ⚠️ NOT DETECTED - options built elsewhere
jwt.sign(payload, secret, getSignOptions());Payload Without iat When noTimestamp Is Not Used
Why: jsonwebtoken adds iat unless noTimestamp is set, so for jsonwebtoken the rule only asks about a noTimestamp chosen at runtime (no-timestamp-manipulation owns the literal true).
// ⚠️ EDGE CASE - Most libraries auto-add iat, so this is usually safe
jwt.sign({ sub: 'user' }, secret); // iat added automaticallyMitigation: Rely on library defaults. Use require-max-age for verification-side checks.
Library Without Auto-iat
Why: Some JWT libraries don't auto-add iat, which the rule assumes.
// ❌ NOT DETECTED - Library doesn't auto-add iat
import { sign } from 'minimal-jwt-lib';
sign({ sub: 'user' }, secret); // No iat and none added by libraryMitigation: Verify library behavior. Add explicit iat in payload.
Spread of an Unresolvable Value
// ⚠️ NOT DETECTED - the spread source is a call
jwt.sign(payload, secret, { ...getSignOptions() });Further Reading
Use this rule
npm install -D oxlint-plugin-jwt-security{ "jsPlugins": ["oxlint-plugin-jwt-security"], "rules": { "jwt-security/require-issued-at": "error" }}import { defineConfig } from 'vite-plus';export default defineConfig({ lint: { jsPlugins: ['oxlint-plugin-jwt-security'], rules: { 'jwt-security/require-issued-at': 'error', }, },});oxlint-plugin-jwt-security re-exports eslint-plugin-jwt-security/oxlint: the same rules under the same IDs, and no ESLint install. Why oxlint.
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.