Three segments, one JSON.parse each, and nothing more
A JWT is three base64url segments joined by dots — header, payload, and signature — and that is exactly what this tool works with. It splits the token on its dots, and if there are not exactly three parts, it stops immediately with "Invalid JWT: must contain 3 parts." rather than guessing at a partial token. The first two segments are base64url-normalized (dashes and underscores swapped back to standard characters, padding restored based on the segment's length) and run through atob, then JSON.parse, to produce the header and payload objects shown on the right. The third segment — the signature — is never decoded or interpreted at all; it is returned and displayed as the plain string it already is.
What this decoder actually verifies: nothing
There is no cryptography anywhere in this tool's decoding logic — no HMAC, no RSA, no ECDSA, nothing that checks whether a signature is genuine. The "Verify Signature" panel accepts a secret key, but typing one does not run a check against it; it only reveals a note stating that real signature verification requires cryptographic libraries this page does not include. Decoding a token and verifying it are two different operations, and this page performs only the first. A token that decodes cleanly here has a well-formed structure — that is all it proves.
What the "Expired" and "Valid" badge actually checks
The status badge reads a single claim: exp. If the payload has no exp field at all, the badge does not appear — there is nothing to compare against. When exp is present, the badge compares it against the current time and reports "Expired" or "Valid" accordingly. "Valid" means exactly one thing here: the token has not passed its own stated expiry time. It says nothing about who issued the token, whether its signature is genuine, or whether it has been altered since it was signed — an attacker who edits a payload and re-encodes it produces a token that decodes here identically to a real one, badge included, because nothing about decoding or the expiry check can detect that.
iss, sub, aud, and nbf are displayed, not enforced
The Standard Claims panel surfaces every recognized field it finds — issuer, subject, audience, expiration, not-before, issued-at, and JWT ID — converting the three time-based ones into a readable local date and time. None of these values are checked against anything else on this page: a token whose nbf (not valid before) claim lies in the future is still shown, not rejected, and an aud value is displayed exactly as written with no comparison against an expected audience. Reading these claims here is for inspection, not for deciding whether a token should be trusted by an application.
One token, decoded exactly
The page loads with a sample token already pasted in. Its header decodes to {"alg":"HS256","typ":"JWT"} and its payload to {"sub":"1234567890","name":"John Doe","iat":1516239022,"exp":1916239022} — that exp claim corresponds to a date decades from now, which is why this particular demo token always shows "Valid" no matter when the page is loaded. That is a property of this one sample token, not something the page arranges — a token with an exp in the past would show "Expired" the same way.
Before pasting a production token here
Decoding runs entirely in the browser and nothing is sent to a server — that part of the client-side claim is accurate. But a JWT payload is only base64-encoded, not encrypted, so anything in it — user IDs, email addresses, internal role names — is fully readable the moment it is decoded, by this tool or any other. Pasting a live production token here (or into a screenshot, a bug report, or a chat message) exposes that payload in plain text to whoever sees it next; a redacted or already-expired token is the safer choice for demonstrating a problem to someone else.