A JWT rejected for a time-related reason is not always simply “expired.” Inspect the exp (expiration) and nbf (not-before) claims first, check iat (issued-at) for unexpected timestamps, and compare those values with the clock used by the service that validates the token. Then check the verifier's configured rules: decoding a token can reveal its claims, but it cannot tell you whether the server accepts it.
For a quick visual inspection, use Letinix JWT Inspector to decode a token's header and payload and compare its time claims. It does not verify signatures or determine whether an API will accept the token. Avoid pasting live production credentials into websites; use a synthetic token or a trusted local workflow.
1. Read the three time claims correctly#
JWT registered time claims use NumericDate values: numbers representing seconds since 1970-01-01T00:00:00Z. They are not JavaScript millisecond timestamps.
| Claim | Meaning | Diagnostic question |
|---|---|---|
| exp | Expiration time | Is the current time at or after the token's expiration? |
| nbf | Not-before time | Is the token being presented before it is allowed to be accepted? |
| iat | Issued-at time | Does the issue time look unexpectedly far in the future or inconsistent with the issuer's behavior? |
These claims are optional in the general JWT specification. Your application or token profile may require particular claims, so absence is not automatically a universal protocol error. Follow the contract for the issuer and the API you are calling. RFC 7519 defines the registered claims and their time semantics.
2. Check exp before changing token logic#
The exp claim says when the token must no longer be accepted for processing. Under RFC 7519, the current time must be before the expiration time; at the expiration instant, the token is expired. A verifier may allow a small clock-skew leeway, but that is implementation policy, not a reason to assume every verifier behaves the same way.
Common causes of an apparent expiration failure include:
- Seconds versus milliseconds: a Unix timestamp in seconds has roughly ten digits today; JavaScript's Date.now() returns milliseconds. Comparing them directly produces a wildly incorrect result.
- The token really expired: the client reused a cached access token after its expiry, or the request was delayed until after exp.
- Different clocks: the issuer, API, container, or host has a clock that is out of sync.
- Unexpected issuer configuration: the token lifetime may be shorter than the client assumes, or a different token may be attached to the request.
- Verifier policy differs from the inspector: an inspection page can show time claims, but only the server's configured validation logic determines acceptance.
For a local sanity check in JavaScript, convert seconds to milliseconds before creating a Date:
const exp = 1_900_000_000; // illustrative NumericDate in seconds
const expiresAt = new Date(exp * 1000);
const expired = Date.now() >= exp * 1000;
console.log(expiresAt.toISOString());
console.log({ expired });
The example only evaluates a timestamp. It does not validate a JWT, verify its signature, or establish that a token should be trusted.
3. Check nbf when a token is “not yet valid”#
The nbf claim identifies the time before which a token must not be accepted. If the verifier's clock is earlier than nbf, the token may be rejected even when exp is still in the future.
Check these points:
- Convert nbf from seconds to a UTC timestamp and compare it with the current time on the validating service.
- Check whether the issuer intentionally creates tokens that become valid in the future.
- Compare the issuer's clock with the API host's clock, including containers and virtual machines.
- Inspect the verifier's documented clock-tolerance setting rather than adding arbitrary leeway in application code.
A token can therefore be unexpired but still not currently valid under the verifier's time rules.
4. Use iat as a clue, not an expiry rule#
iat records when a token was issued. By itself, it does not define the token's expiry. An application may use it to reason about token age or enforce a profile-specific policy, but that is distinct from the core meaning of exp.
If iat appears to be in the future, investigate clock synchronization, token generation, and whether the value is actually encoded in seconds. Do not conclude from iat alone that a token is invalid unless the relevant validation policy says so.
5. A practical debugging sequence#
Use this order to avoid changing the wrong component:
- Capture the exact failing request. Confirm which bearer token was sent and which service rejected it. Do not log the full production token into shared logs or tickets.
- Decode the header and payload safely. Use a synthetic token, a trusted local debugger, or JWT Inspector to read the claims. Decoding is an inspection step, not a trust decision.
- Check claim types and units. Confirm exp, nbf, and iat are numeric timestamps in seconds where the issuer's token profile expects NumericDate values.
- Compare against the verifier's clock. Use UTC timestamps and check the time synchronization of the actual runtime that performs validation, not just your laptop.
- Check the verifier's policy. Review required claims, configured tolerance, issuer and audience rules, allowed algorithms, trusted keys, and signature verification.
- Reproduce with a synthetic token or test environment. Avoid repeatedly retrying a live credential or sharing it in a bug report.
- Inspect the rejection reason in server-side diagnostics. A generic HTTP 401 does not identify the failed check by itself. The API's logs and validation configuration are needed to distinguish expiry from signature, issuer, audience, or other policy failures.
6. Why a decoded token can still fail authentication#
A decoded payload is readable data, not proof of authenticity. A JWT's header and payload may be Base64url-decoded without the verifier accepting the signature, issuer, audience, or time claims. For signed tokens, the receiving service must verify the signature according to its configured algorithm and trusted key, then apply the applicable claim and application checks. JWT best-current-practice guidance also warns against trusting an algorithm merely because an untrusted token header names it. See RFC 8725.
Keep the diagnostic stages separate:
- Decode: read the header and payload.
- Verify: validate the cryptographic signature or MAC using the expected algorithm and appropriate key.
- Validate claims: apply the issuer, audience, time, token-type, and other requirements for the token profile.
- Authorize: decide whether the authenticated principal may perform the requested operation.
A token can pass one stage and fail a later one. If the time claims look correct, continue through the verifier's logs and configuration rather than treating a decoded token as proof that the token is valid.
7. What to check when the timestamps look right#
If the token's time claims appear plausible but the API still returns 401, widen the investigation without assuming the time claims are the cause:
- Is the expected token being sent, rather than an ID token where an access token is required, or a token from another environment?
- Does the iss value match the issuer configured by the API?
- Does the aud claim identify the intended recipient according to that API's policy?
- Does the signature verify with the expected algorithm and trusted key set?
- Is the token type or required claim set specific to this API?
- Is the service returning 401 for a different authentication failure, or is authorization failing later and being surfaced similarly?
The answers depend on the application's token profile and verifier implementation. The JWT specification does not require every application to accept the same set of claims or token types.
References#
RFC 7519: JSON Web Token (JWT) — IETF
RFC 8725: JSON Web Token Best Current Practices — IETF
Troubleshooting JWT validation — Google Cloud documentation



