What a decoded JWT proves
A JWT decoder shows you what a token claims. It cannot show you whether those claims are true. This guide covers what the three segments are, what decoding does and does not establish, and the attacks that live in the gap between the two.
Three segments, two of which are just JSON
A JWT in its common form is a JWS: three base64url segments separated by dots. The first is a header, the second a payload, the third a signature.
The header and payload are ordinary JSON objects that have been base64url-encoded. Encoded, not encrypted. Anyone holding the token can read both, instantly, with no key — that is not a flaw, it is the design. A JWT is a signed statement, not a sealed envelope. The signature guarantees that the statement has not been altered; it does nothing to keep it private.
The consequence is worth stating plainly because it is routinely missed: never put anything confidential in a JWT payload. Not a password, not a full national identifier, not internal system details. Assume the payload is public, because to anyone who has the token, it is.
The third segment is the signature, computed over the first two. It is the only part that carries any security value, and it is the part a decoder cannot evaluate.
What decoding proves: nothing
This is the point of the whole guide. Decoding a JWT parses two base64url strings into JSON. It confirms that the token is well-formed. It does not confirm that the token is genuine, that it was issued by the party named in the "iss" claim, that the claims have not been edited, or that it was ever valid.
Anyone can construct a token. Take any JWT, change "role": "user" to "role": "admin", re-encode the payload, staple any signature on the end, and a decoder will display your edited claims exactly as confidently as it displayed the original. It has no way to know the difference, because checking the difference is a different operation requiring a key the decoder does not have.
So when a decoder — this one, or any other — shows you "exp: 2026-01-01", what it is really telling you is: this token contains a claim that it expires on that date. Whether that claim means anything depends entirely on whether the signature is valid, which has not been checked.
This tool decodes only, and says so on the page, next to the results, every time. Not in a footnote. The reason is that a decoder that stays quiet about this is training its users to read unverified data as though it were verified, and that habit is the root of a whole family of authentication bugs.
Why this tool does not offer verification
Verification needs three things a web page cannot responsibly have: the issuer’s key, the algorithm pinned in advance, and a policy about what to reject.
The key is the obvious problem. For HMAC algorithms (HS256 and friends) the key is a shared secret — the same secret used to create tokens. Pasting it into a web page means pasting a credential that can mint valid tokens into a web page. For RSA and ECDSA the public key is not secret, but you would still need to fetch the right one from the right JWKS endpoint and trust that you had.
The algorithm is the subtle problem, and the source of two well-known attacks. The first is alg: "none": the header claims the token is unsigned, and a verifier that honours the header rather than its own configuration accepts anything. The second is RS256-to-HS256 confusion: the attacker takes a public key — which is, by definition, public — changes the header to say HS256, and signs the token using that public key as the HMAC secret. A verifier that reads the algorithm from the token and looks up "the key" will validate it.
Both attacks come from the same mistake: letting the token tell the verifier how to check the token. A correct verifier ignores the header’s algorithm and uses the one it was configured with. That is a decision belonging to the system that trusts the token — not to a convenience tool, and not to whoever pasted something into a form.
Why not to paste production tokens anywhere
An access token is a bearer credential. That is what "bearer" means in the Authorization header: whoever bears it, is you. There is no second factor and usually no way to tell a stolen token from a legitimate one. Until it expires, it is a working key to your account.
So pasting a live token into any web page is handing a credential to that page. This one decodes everything locally and makes no network request after the page loads — you can confirm that in your browser’s network panel, and you should, because it takes ten seconds. But notice what that argument actually is: a claim, on a website, that the website is trustworthy. Every site that exfiltrates tokens makes exactly the same claim, and a visitor cannot tell the difference at a glance.
The safe habit does not depend on judging sites correctly. Use expired tokens, test-environment tokens, or tokens you have minted for the purpose. If you have already pasted a production token somewhere — anywhere — rotate it. Revocation is cheap; an incident is not.
The same applies with more force to signing keys. There is no legitimate reason to type an HMAC secret or a private key into a web page, and any site asking for one to "verify" your token is asking for the ability to forge tokens. That is the concrete reason this tool has no verification feature: the feature requires the request.
Reading the claims that matter
RFC 7519 registers a small set of claim names. "iss" is the issuer, "sub" the subject the token is about, "aud" the intended audience, "exp" the expiry, "nbf" the earliest valid time, "iat" the issue time, and "jti" a unique id for replay detection. Everything else is application-specific.
The time claims are NumericDate values: seconds since the Unix epoch, not milliseconds. This trips people up constantly, because most JavaScript time values are milliseconds. A token that appears to expire in 1970 has usually been given a millisecond value; one that appears to expire in the year 55000 has usually had a second value multiplied by 1000 somewhere.
"aud" deserves particular attention when you are debugging. A token that is perfectly valid can still be the wrong token, because it was issued for a different audience. A verifier that checks the signature but not the audience will accept a token minted for another service entirely — which is a real privilege-escalation path in systems that share an identity provider.
This tool renders the time claims in UTC, marks an expired token as expired, and pairs that with a reminder that the expiry claim only means something if the signature is valid. The reminder is there because "it says it has not expired" is the exact moment the unverified-data habit does its damage.
A short checklist for the system doing the trusting
If you are writing the code that accepts tokens rather than just inspecting one, the following is the short version of what a correct verifier does.
- Verify the signature first, with a key you obtained out of band, before reading any claim.
- Pin the algorithm in your own configuration. Never read it from the token header. Reject "none" unconditionally.
- Check "exp" and "nbf" against a trusted clock, with at most a small tolerance for skew.
- Check "iss" and "aud" against the values you expect. A valid signature on a token meant for someone else is still the wrong token.
- Use a vetted library for your platform rather than assembling this yourself. Every item on this list is on it because implementations have got it wrong.
- Keep token lifetimes short, and have a revocation path. Short-lived tokens limit the damage of the leak you have not noticed yet.
What happens to what you paste
- Every conversion, hash, decode and diff runs in your browser tab. No input is uploaded, logged or stored on a server, because there is no server involved once the page has loaded.
- Hashes come from the browser’s own Web Crypto implementation, and UUIDs from its cryptographically secure random generator. Neither involves a network call.
- Nothing you type is written to local storage or a cookie. Reloading the page discards it; closing the tab discards it.
- There is no analytics script, no advertising script and no third-party request of any kind. You can confirm all of this in your browser’s network panel — the page makes no requests after it loads.
- That said: a JWT or an API key is a live credential. The safe habit is never to paste one into a web page you did not write, however trustworthy its claims — including this one.
Questions
Does this tool verify the signature?
No, and it never will. It decodes the header and payload and shows you what they contain. It does not check the signature, so nothing it displays proves the token is authentic, unaltered, or issued by whoever it names.
Then how do I know a token is genuine?
By verifying the signature with the issuer’s key, using a vetted library, with the algorithm pinned in your own configuration rather than read from the token. That is work for the service that trusts the token, in an environment that legitimately holds the key.
Is my token sent anywhere when I decode it here?
No. The decoding happens in your browser tab using the page’s own JavaScript, and the page makes no network requests after it loads. You can verify this in your browser’s network panel. You should still not paste production tokens into web tools as a matter of habit, because that habit has to work on sites that are not honest about it.
Why can anyone read my JWT payload?
Because the payload is base64url-encoded, not encrypted. A JWS is a signed statement, not a sealed one. If you need the contents to be unreadable you need JWE, the encrypted token format — and then a decoder cannot show you anything at all without the key.
What is alg: "none"?
A header value declaring that the token is unsigned. It exists in the specification for contexts where integrity is guaranteed by other means, and it is a standing trap: a verifier that trusts the header’s algorithm will accept any token that claims "none". This tool flags it whenever it appears.
My token has five segments and will not decode. Why?
Five segments means a JWE — an encrypted token — rather than a signed JWS. Its contents cannot be read without the decryption key, so there is genuinely nothing for a decoder to show. This tool identifies that case explicitly instead of reporting a vague parse failure.
The expiry looks wrong by a factor of 1000.
JWT time claims are NumericDate: seconds since the epoch, not milliseconds. A value produced by Date.now() is a thousand times too large. The timestamp utility in this toolkit converts between the two and always tells you which unit it used.
Is it safe to store a JWT in localStorage?
It is a trade-off, not a yes or no. localStorage is readable by any JavaScript running on your origin, so a single XSS vulnerability exfiltrates the token. An httpOnly cookie is not readable by JavaScript but needs CSRF protection. The honest summary is that neither is free, and the decision belongs with the threat model of your application.
Limitations
- This tool decodes only. It does not verify signatures, and that is a permanent design decision rather than a missing feature — see the guide above for why.
- Encrypted tokens (JWE, five segments) cannot be decoded at all without the key. The tool identifies them and stops.
- Nested JWTs — a token whose payload is itself a token — are not unwrapped automatically. Decode the inner token as a separate step.
- Claim meanings beyond the registered set defined in RFC 7519 are application-specific, so the tool shows their values without interpreting them.
- An expiry shown here reflects only what the token claims about itself. Whether that claim is meaningful depends on a signature this tool does not check.
- Tokens larger than 200,000 characters are refused. Any real JWT is orders of magnitude smaller.
Last reviewed 2026-09-13.