Why is my JWT expired?
The exp claim is a NumericDate: the number of seconds —
not milliseconds — since 1970-01-01T00:00:00Z, as defined by
RFC 7519 §4.1.4.
Once the verifier's clock passes that instant, the token is rejected, and no amount of
re-sending it will help. An expired JWT cannot be extended; it can only be replaced by a
freshly issued one.
What iat, exp and nbf actually mean
-
iat— issued at. When the authorization server minted the token. Useful for working out the intended lifetime:exp - iat. -
nbf— not before. The token is invalid until this instant. Most issuers set it equal toiat, or a few seconds earlier to absorb clock skew between machines. -
exp— expires. The moment the token stops being accepted. A verifier is allowed a small leeway here too, which is why a token can look "just expired" locally and still work against the API for another few seconds.
The exp-in-milliseconds trap
A surprising number of hand-rolled issuers write Date.now() straight into
exp. That produces a 13-digit value where the spec wants 10, so the claim
reads as a date roughly fifty thousand years in the future and the token effectively
never expires. This decoder flags 13-digit claims rather than quietly rendering the
year 58,391. If you are not sure which unit a number is in,
the digit-count check settles it.
Convert an exp claim to a datetime in your own code
// JavaScript — exp is seconds, Date wants milliseconds
const { exp } = JSON.parse(atob(token.split('.')[1]))
const expiresAt = new Date(exp * 1000)
const expired = Date.now() >= exp * 1000
# Python
import base64, json, time
payload = json.loads(base64.urlsafe_b64decode(token.split('.')[1] + '=='))
expired = time.time() >= payload['exp']
Your token never leaves this page
Decoding a JWT needs no server: the header and payload are base64url-encoded JSON, so
the whole job is two string operations and a JSON.parse. This page issues
no network request when you paste — open DevTools and watch the Network tab if you would
rather verify than trust. That matters because production bearer tokens routinely end up
in "just paste it into this online decoder" workflows.
Frequently asked questions
What format is the JWT exp claim?
A NumericDate: the number of seconds — not milliseconds — since 1970-01-01T00:00:00Z, as
defined by RFC 7519 section 4.1.4. A valid exp for any current token is
therefore ten digits, and rendering it in JavaScript means multiplying by 1000 first,
because Date expects milliseconds: new Date(exp * 1000).
What do the iat, exp and nbf claims mean?
iat is "issued at": when the authorization server minted the token.
nbf is "not before": the token is invalid until that instant, and most
issuers set it equal to iat or a few seconds earlier to absorb clock skew.
exp is "expires": the moment the token stops being accepted. A verifier is
allowed a small leeway on exp, which is why a token can look just-expired
locally and still work against the API for a few more seconds.
Does my token get sent to a server?
No. A JWT's header and payload are base64url-encoded JSON, so decoding is two string
operations and a JSON.parse — no server needed. This page issues no network
request when you paste; open DevTools and watch the Network tab to verify rather than
trust.
Does this decoder verify the token's signature?
No, and deliberately so. Verifying would require your signing key or JWKS endpoint, and a page that asked for your signing key would be indistinguishable from a phishing page. This is a decoder, not a validator — treat the claims as untrusted until your own backend has checked the signature.
Why does my token show as valid here but fail against my API?
Usually clock skew or an nbf in the future. Your machine and the verifier
disagree about "now" by a few seconds, and the verifier is stricter. The claims table
above shows the raw epoch value, so you can compare it against the server's clock
directly.
Can I decode an encrypted token?
No. A JWE has five dot-separated segments instead of three and its payload is ciphertext; no decoder can read it without the decryption key.