What a JWT is
A JSON Web Token (JWT) is a compact, URL-safe token made of three Base64url-encoded parts joined by dots: header.payload.signature. The header says how the token is signed, the payload carries the claims (data) and the signature lets a server verify the token wasn't tampered with.
This decoder splits the token, Base64url-decodes the first two parts and pretty-prints them as JSON. It does not verify the signature, because that requires the secret or public key — which you should never paste into any website.
Worked example
For the sample token, the parts decode to:
{"alg":"HS256","typ":"JWT"} — signed with HMAC-SHA256.{"sub":"1234567890","name":"Calcool","iat":1700000000}.Signature and security
Anyone can read a JWT's payload — it's only encoded, not encrypted — so never store secrets in it. The signature is what proves authenticity, and only the server with the key can validate it. Standard claims include exp (expiry), iat (issued at), sub (subject) and iss (issuer); this tool flags whether the token is expired based on exp.
Registered claims you'll actually see
RFC 7519 defines seven registered claim names. They're optional, but interoperable tools agree on what they mean, so knowing them lets you read almost any token at a glance:
| Claim | Name | What it carries |
|---|---|---|
iss | Issuer | Who minted the token (e.g. https://accounts.google.com). Your code should check this matches the provider you trust. |
sub | Subject | The user or entity the token is about — usually a stable user ID, not an email. |
aud | Audience | Who the token is for. Reject the token if your service isn't in aud. |
exp | Expiration | Unix seconds after which the token is invalid. Always enforce server-side. |
nbf | Not before | Token is invalid until this time — useful for tokens issued ahead of activation. |
iat | Issued at | When it was created. Handy for detecting tokens that are suspiciously old. |
jti | JWT ID | A unique identifier you can store to make a token one-time-use (replay protection). |
Everything else in the payload is a private claim — name, email, roles, scope and so on. Those are app-specific and not standardised, so two providers may format them differently.
Reading the alg header — and the traps
The header's alg tells you (and the verifier) how the signature was produced. The three families you'll meet:
- HS256 / HS384 / HS512 — HMAC with a shared secret. Both signer and verifier hold the same key. Simple, but the secret must stay on the server.
- RS256 / RS384 / RS512 — RSA signatures. The issuer signs with a private key; anyone can verify with the public key. This is what OpenID Connect providers (Google, Auth0, Microsoft) use.
- ES256 / ES384 — ECDSA, the same public/private idea as RSA but with shorter keys and smaller signatures.
Two classic, real-world vulnerabilities are visible right in the header, so it's worth knowing them:
"alg":"none" has no signature at all. Early libraries accepted these, letting attackers forge any payload. A correct verifier rejects none unless explicitly, deliberately allowed.Troubleshooting a token that won't validate
When a backend rejects a JWT, decoding it here usually reveals which check failed:
| Symptom | Likely cause |
|---|---|
| "Token expired" | exp is in the past. Compare it to the current Unix time; allow a small clock-skew window (typically 30–120 s). |
| "Invalid audience" | aud doesn't list your service's client ID. Common when a token minted for one app is reused on another. |
| "Signature verification failed" | Wrong key, wrong algorithm, or the token was modified. Decoding still works because decoding never checks the signature. |
| Decoder shows garbage / fails to parse | A part isn't valid Base64url — often a copy-paste truncation, or someone pasted a Base64 (not Base64url) string with + and / instead of - and _. |
exp. A short expiry plus a server-side jti blocklist, or a plain database session, is often the better choice.