Skip to content

fix: respect an iat of 0 instead of overwriting it with the current time (#874) - #1043

Open
jaideeppyne wants to merge 1 commit into
auth0:masterfrom
jaideeppyne:fix/iat-zero-respected
Open

fix: respect an iat of 0 instead of overwriting it with the current time (#874)#1043
jaideeppyne wants to merge 1 commit into
auth0:masterfrom
jaideeppyne:fix/iat-zero-respected

Conversation

@jaideeppyne

Copy link
Copy Markdown

Description

Fixes #874. Signing a token with iat: 0 (the Unix epoch) silently discards the value and stamps the current time instead.

The cause is a falsy-zero bug at two coupled sites:

// sign.js:185
const timestamp = payload.iat || Math.floor(Date.now() / 1000);
// lib/timespan.js:4
var timestamp = iat || Math.floor(Date.now() / 1000);

0 is falsy, so 0 || now evaluates to now. Because that same timestamp is the base timespan() uses to derive exp and nbf, those claims are corrupted too whenever iat is 0.

Reproduction on master:

const jwt = require('jsonwebtoken');
jwt.decode(jwt.sign({ iat: 0 }, 'secret', { algorithm: 'HS256', expiresIn: 86400, notBefore: 60 }));
// actual:   { iat: 1787105508, nbf: 1787105568, exp: 1787191908 }   ← all wrong
// expected: { iat: 0,          nbf: 60,          exp: 86400 }

A non-zero iat (e.g. 100) already works — the bug only bites for 0, which is a perfectly valid NumericDate (isNumber(0) === true, so it passes claim validation).

Fix

Replace the falsy check with an explicit numeric guard at both sites:

const timestamp = (typeof payload.iat === 'number' && !isNaN(payload.iat)) ? payload.iat : Math.floor(Date.now() / 1000);

Both sites must change — patching only sign.js leaves timespan() re-applying its own 0 || …, so exp/nbf would stay corrupted.

The !isNaN guard deliberately preserves the existing contract already encoded in test/claim-iat.test.js: the only falsy number values are 0 and NaN, so this rescues 0 while keeping NaN → current time and Infinity → null unchanged.

Tests

Added to test/claim-iat.test.js:

  • a signing-table case asserting iat: 0 is preserved (decode(token).iat === 0);
  • a dedicated test asserting exp/nbf are anchored to iat: 0 (exp === 86400, nbf === 60) — this covers the lib/timespan.js site.

Both fail on master and pass with the fix; the full claim-iat suite (41 tests) is green, and the pre-existing NaN/Infinity cases are unchanged.


Disclosure: this change was prepared with AI assistance and reviewed/verified by me before submission.

Signing a token with `iat: 0` (the Unix epoch) silently discarded the value
and used the current time instead, because both `sign.js` and `lib/timespan.js`
used `iat || Math.floor(Date.now() / 1000)` — and `0` is falsy. Since that
timestamp is also the base `timespan()` uses to derive `exp`/`nbf`, those
claims were corrupted too whenever `iat` was 0.

Guard with an explicit numeric check (`typeof iat === 'number' && !isNaN(iat)`)
at both sites. `0` is now preserved, while the existing contract is kept:
`NaN` still falls back to the current time and `Infinity` still serializes to
`null`. Both sites must change, otherwise `timespan()` re-applies its own
`0 || …` and `exp`/`nbf` stay wrong.

Fixes auth0#874.
@jaideeppyne
jaideeppyne requested a review from a team as a code owner August 19, 2026 02:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IAT payload value is not respected if value is zero

1 participant