Developer tools · Unix timestamp converter
Why your JWT expires immediately: exp is in seconds, not milliseconds
· Why it matters
jwt timestamps security
RFC 7519 defines exp, iat and nbf as seconds since the epoch, and mixing that with a millisecond clock makes tokens expire instantly or never. This post explains the claim format and how to check a token's times.
Issued at 10:00, expired at 10:00 — a token rejected on its first use and a server clock that was not the problem
A token rejected on its first request invites suspicion of server skew, but inspect the raw claims before changing clocks. If one component generated `exp` from a millisecond clock while another compares NumericDate seconds, the values differ by three orders of magnitude. No ordinary synchronization adjustment explains that gap.
Use a throwaway or redacted token because a bearer token is a credential. ToolAcre’s JWT decoder reads payload data but deliberately does not verify signatures. Copy the numeric time claim into the timestamp converter only after preserving the original test fixture and its intended lifetime.
What RFC 7519 says — NumericDate as seconds since 1970-01-01T00:00:00Z, and why it is a number rather than a string
The adjacent published JWT article already states the crucial contract: `exp` NumericDate counts seconds from the Unix epoch. Repeating its standards explanation would add no value here. The practical question is whether each producer, serializer, verifier and test fixture honors that same scale.
Look for explicit boundary code: a millisecond clock divided into seconds when issuing, and a seconds comparison when validating. The claim should remain a number rather than a formatted date used for arithmetic. Human-readable UTC is a diagnostic projection, not the token’s authoritative representation.
Pin this contract in issuer and verifier tests with a nonzero value. A test using epoch zero cannot reveal whether either side divided or multiplied by one thousand.
The existing JWT article establishes NumericDate seconds; this article applies that fact to expiry debugging
If a verifier interprets a valid seconds claim as milliseconds, the date falls near 1970 and appears expired. If an issuer writes a current millisecond value into a field later interpreted as seconds, expiry moves far beyond the intended lifetime or beyond a library’s supported range. Which symptom appears identifies which side owns the scale error.
Avoid a “fix” that accepts both forms based on digit count. That turns malformed tokens into a permanent alternate protocol and can hide issuer regressions. Reject values that violate the application’s NumericDate contract, correct generation, and add fixtures that distinguish seconds from milliseconds.
Millisecond mistakes can create immediate rejection or implausibly remote expiry depending on which side is wrong
Decode the payload to expose `exp`, `iat` and `nbf` as raw values before a framework converts them. Compare `exp − iat` to the intended token lifetime in seconds. Check `nbf` separately; a token can be unexpired yet not usable. Do not infer authenticity from reasonable-looking times.
ToolAcre’s decoder reports that signature verification is false, so its output belongs in debugging, never authorization. A modified payload can contain any expiry an attacker chooses. The trusted application verifier must still enforce algorithm, key, issuer, audience and time policy on the original compact token.
Worked example: an exp of 1700003600 — converting it to UTC and local time, checking it against iat, and confirming the lifetime is what you intended
For `iat = 1,700,000,000` and `exp = 1,700,003,600`, subtraction yields 3,600 seconds, or one hour. The converter reads the expiry explicitly as seconds and returns `2023-11-14T23:13:20.000Z`; the issue time is `2023-11-14T22:13:20.000Z`.
Those numbers are unique to this article’s diagnostic example. If selecting milliseconds produces a January 1970 reading, that is expected evidence of the wrong scale. Confirm that the verifier’s current time is also expressed in seconds before concluding the one-hour policy is implemented correctly.
The one-hour difference is computed before formatting, so it remains one hour in every zone. Local displays may differ, but `exp − iat` does not.
Worked example: compare exp 1,700,003,600 with a nearby iat using explicit seconds
A verifier may allow a small application-defined tolerance around time claims to accommodate modest clock differences. This repository does not define a recommended number of seconds, so no universal leeway is prescribed here. The security policy and library configuration are the authorities.
Tolerance should remain tiny relative to a factor of one thousand. Expanding it until a malformed claim passes weakens expiry enforcement and leaves the issuer bug alive. First normalize clock units and synchronization; then decide whether a bounded allowance serves the application’s threat model.
If a tolerance is configured, test values just inside and outside that boundary in seconds. This proves policy independently from any Date or locale rendering.
Tolerance cannot repair a factor-of-1,000 mismatch
Timestamp conversion cannot verify a token’s signature, permitted algorithm, key, issuer or audience. Even a perfectly formatted, future `exp` claim may sit inside a forged token. The JWT decoder is intentionally transparent about this boundary and should be paired with a trusted verifier.
It also cannot establish whether a captured production token has been revoked or whether a session policy overrides its nominal expiry. Debug values with non-sensitive fixtures. If a real incident requires examining credentials, use the authorised environment and handling procedure rather than a general clipboard workflow.
Decoding should happen only with synthetic or safely redacted fixtures during routine debugging. Copying a live bearer credential creates a security problem unrelated to timestamp arithmetic.
Takeaway: exp is ten digits, not thirteen — and how the JWT decoder and the Unix timestamp converter sit in the same tab so you can check a claim in seconds
Treat JWT time claims as seconds at every boundary and test their differences as durations. The timestamp converter turns an individual claim into UTC and local context; the JWT decoder exposes the raw number. Together they explain timing without claiming trust.
The durable correction belongs in issuing and verification code, not in a support runbook that toggles units until a token works. Preserve explicit seconds, reject malformed scale, and keep signature verification as a separate mandatory decision.
That separation also improves observability: generation logs can report a duration policy without exposing tokens, while verification metrics can distinguish expired, premature and invalid-signature outcomes.