JWT Payload Decoder
A JSON Web Token is three Base64url segments joined by dots, and the middle one is readable by anyone holding the token. That single fact explains most JWT security mistakes. This decoder shows you the header and payload without sending the token anywhere.
How to use it
- Paste a token into the input. Bearer prefixes are tolerated.
- The header and payload segments are Base64url-decoded in the page.
- Read the claims. The token is never transmitted, which matters because a live token is a credential.
Decoding is not verifying, and the distinction is the whole security model
Splitting a JWT on dots and Base64-decoding the payload requires no key and proves nothing. Anyone can do it to any token, and anyone can forge a token whose payload claims whatever they like.
What makes a JWT trustworthy is the third segment, the signature, computed over the first two using either a shared secret with HMAC or a private key with RSA or ECDSA. Verifying it requires the corresponding key, which is why verification belongs on your server and not in a browser tool. A tool that offered to verify signatures would have to ask for your signing key, which is precisely the thing you should never paste into a web page.
The practical rule: never make an authorisation decision from a decoded payload. Verify first, then read the claims from the verified result.
The alg:none attack and why libraries got it wrong
The JWT header declares which algorithm signed the token, and the specification includes none as a legitimate value for unsecured tokens. Early libraries read the header, saw none, and concluded no verification was necessary, which let anyone forge any token by setting that header and dropping the signature.
A related attack exploits algorithm confusion. If a server accepts both HMAC and RSA, an attacker can take the public RSA verification key, which is not secret, and use it as an HMAC shared secret. A naive verifier that picks its algorithm from the attacker-controlled header will validate the forgery.
Both are fixed the same way: the verifying code decides the algorithm from its own configuration and rejects anything else, rather than trusting the header. Any current library does this, but the pattern still appears in hand-rolled verification.
The payload is not secret
Base64url is an encoding, not encryption. Every claim in the payload is visible to the client holding the token, to anything that logs the Authorization header, and to anyone who obtains the token in transit or from storage.
This rules out a category of things people put in tokens anyway: internal user identifiers you did not intend to expose, email addresses in a system that promises pseudonymity, role structures that reveal your permission model, and, occasionally, actual secrets. If a claim must stay confidential, it does not belong in a JWT. The encrypted variant, JWE, exists for that, and is considerably less common.
Reading the standard claims
Seven registered claims cover most tokens. The subject, sub, identifies the principal. The issuer, iss, and audience, aud, say who minted the token and who it is for, and both should be checked rather than merely present. The jti is a unique token identifier, useful for building a revocation list.
The three time claims cause the most confusion:
- exp, the expiry time, after which the token must be rejected. It is measured in seconds since the Unix epoch, not milliseconds. Multiplying by 1000 or failing to is the single most common JWT bug, and it produces tokens that expire in 1970 or in the year 56000.
- iat, issued at, which is when the token was created. Useful for rejecting tokens issued before a password change.
- nbf, not before, which makes a token invalid until a future time. Rarely used, and easy to forget to check.
Revocation is the hard part
The appeal of JWTs is that a server can validate one without a database lookup. The cost is that it cannot invalidate one either. A token remains valid until it expires, regardless of whether the user logged out, changed their password, or had their account suspended.
The usual mitigations are short expiry times with refresh tokens, which reduces the exposure window rather than closing it, and a denylist keyed on jti, which reintroduces the database lookup the design was meant to avoid. There is no clean answer, which is why session cookies backed by server-side state remain a reasonable choice for many applications.
At a glance
| Decodes | Header and payload segments |
|---|---|
| Encoding | Base64url, RFC 7515 |
| Signature verification | Not performed, requires your key |
| Transmitted | Nothing, decoding is local |
Frequently asked questions
Does this verify the signature?
No. Verification needs the signing key, and pasting a signing key into a web page is exactly the mistake you should avoid. Verify server-side with a library that pins the expected algorithm.
Is it safe to paste a live token here?
Decoding happens in your browser and the token is not transmitted, which you can confirm in the network tab. It is still a credential, so treat it with the same care you would a password.
Why does my expiry look wrong?
Almost always a seconds-versus-milliseconds error. The exp claim is Unix seconds. JavaScript Date expects milliseconds, so a value that has not been multiplied by 1000 lands in January 1970.
Can I put private data in the payload?
No. The payload is encoded, not encrypted, and anyone holding the token can read it. Use JWE if you genuinely need confidentiality, or keep the data server-side and reference it by identifier.
Read more
Tokens, hashes, and identifiers — Why a UUID is not a secret, a hash is not encryption, and a JWT you have not verified is just a base64 string.