How to Decode a JWT Safely: Decoding Isn’t Verifying
A JWT looks like random characters, but two of its three parts are just Base64url-encoded JSON that anyone can read. Decoding tells you what a token claims. It does not tell you whether the claims are true.
The three parts
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJzdWIiOiIxMjMiLCJleHAiOjE3OTQ0MDAwMDB9 . <signature>- Header — the algorithm (
alg) and token type, as JSON. - Payload — the claims: who the token is about and when it expires.
- Signature — proves the first two parts were issued by someone holding the key. It is not readable JSON.
Each part is Base64url-encoded (RFC 4648 §5): like Base64 but with - and _ instead of + and /, and usually without = padding. That is why a normal Base64 decoder sometimes fails on JWTs.
Common claims (RFC 7519)
| Claim | Meaning |
|---|---|
iss | Issuer — who created the token |
sub | Subject — usually the user ID |
aud | Audience — which service should accept it |
exp | Expiry time, in seconds since 1 January 1970 (UTC) |
iat | Issued-at time, same unit |
nbf | Not valid before this time |
exp is in seconds, not milliseconds. If you compare it with JavaScript’s Date.now() (milliseconds), multiply by 1000 first. Our timestamp converter shows both.
Decoding is not verifying
Anyone can create a token with any payload. Only checking the signature with the right key — on your server, with a maintained JWT library — proves it is genuine. Also reject alg: none (an “unsecured” token defined in RFC 7518) and make sure your server only accepts the algorithm it expects.
Why “where” you decode matters
An access token is a password with an expiry date: whoever holds it can act as the user until it expires. Pasting live tokens into a website that sends them to a server leaks them. Our JWT Decoder decodes in your browser and sends nothing; you can confirm that with your browser’s network tab. For production secrets, prefer short-lived test tokens anyway.