JWT Decoder — Read the Header and Payload and What exp Actually Says
Paste a JSON Web Token and it splits the three base64url segments, prints the header and payload as formatted JSON, and turns iat, nbf and exp into real dates with a plain answer to whether the token is still inside its own validity window. No key is asked for and no signature is checked. Everything runs locally in your browser.
A · Header
B · Payload
C · Claim by claim
| Claim | Value | Reading |
|---|
Three segments, three time claims
What a JWT actually is
A JSON Web Token is not encrypted and not compressed and not stored anywhere: it is two JSON documents written in base64url, plus a signature over them. Anyone holding the token can read both documents in a second — which is exactly why nothing secret belongs in them. The three segments are:
- Header — which algorithm signed this (
alg), what the payload is (typ), and often a key hint (kid,jku) or an embedded certificate chain (x5c). - Payload — the claims:
sub(whose token),iss(who minted it),exp/nbf/iat(its validity window),scope/roles, plus whatever the issuer adds. - Signature — bytes produced over
header + "." + payloadwith the issuer’s key. This is the only thing that stops a reader from editing claim 2 and claiming to be somebody else.
Because segments 1 and 2 are merely encoded, a decoder needs no key and no round trip to anybody. Decoding stops there.
Reading the three time claims
iat, nbf and exp are counts of seconds since 1970-01-01 00:00:00 UTC, never local strings, and they are converted with your browser’s own timezone. Each one gets three readings: local wall-clock time with its UTC offset, the same instant in UTC, and the distance from this moment (expired 12 min ago, still valid for 2 h 4 min) — the relative form is what you actually want when debugging a token that “should still work”.
The verdict line at the top only ever says what the token claims about itself. A server can reject a perfectly unexpired token because the session was revoked, the user was deleted, the aud does not match, or the key was rotated. Equally, an expired token that was never checked stays usable — the expiry is a promise the issuer makes and the receiver is free to ignore or honour.
Anything with more digits than seconds should have is treated as milliseconds and says so: values above 1e11 seconds land past the year 5138, so 1750000000000 is almost certainly Date.now() written straight into a claim that wanted Date.now()/1000|0.
Why the signature is left alone
Verifying a signature means recomputing it, and that means the key. For HS256 the key is a shared secret that must never appear in a browser; for RS256/ES256 the public key has to be fetched from the issuer, which would send your token’s metadata over the network. So no key is asked for, your token is carried nowhere, and “signature valid” never appears — the segment is shown with its byte length and one honest sentence about what it cannot tell you.
What you can read out of the header is a warning sign: alg: none means the token asserts that no signature is needed, and any implementation that accepts one lets anyone forge anyone. A missing alg is worse in practice, because the classic library bug was to pick the algorithm from the token itself.
When the result needs a second look
- A JSON array or a scalar payload is printed as decoded, with no claims table — technically parseable, almost never a real token.
- Duplicate keys in the JSON collapse to the last occurrence when parsed, so a token crafted with two
"exp"keys shows only one. That is JSON’s rule, not a choice made here, and it is one reason hand-written tokens are dangerous. - Integers past 15 significant digits round:
sub: 1234000000000000001may come back with different trailing digits, which the note lists explicitly. "b64": false(RFC 7797) means segment 2 is plaintext JSON, not base64url; that flag is honoured once the header is parsed, and the note says so.- Non-UTF-8 payloads report how many byte sequences could not be decoded and show each one as the replacement character — the rest of the segment is kept, not quietly dropped; some issuers stuff binary identifiers into claims, and no rendering of those means anything.
Questions
Q: Can I use this to forge a token? No. Changing any claim without the key breaks the signature, and no key is held or needed. What is shown is what a token says — which is also the answer to “why can I read someone else’s user id out of a JWT in a public response”: you can, and that is a bug in whatever exposed it, not in JWT.
Q: Why does it say my token is fine but the server returns 401?
“Still inside its own window” is not the same as “will be accepted”. The receiver also checks the signature, the iss/aud allow-list, the clock skew allowance, and often a revocation store. The verdict here speaks only to the claims written in the payload.
Q: My pasted value ends with = and nothing arrived.
Padding is legal to decode and gets decoded (the header is unpadded, so = at the end is a copy from a base64 field rather than a real JWT habit). If the segments are still rejected, the text is probably base64 of something else — try the Base64 page.
Q: Does the token get sent anywhere? Splitting, decoding and formatting are loops running in the browser: no key is asked for, and no server is handed the token. The token you paste does not leave this device.