How to Decode a JWT and Read Its Claims
June 19, 2026 · DevTools
A JWT looks like gibberish until you realize it is just two JSON objects and a signature, each Base64url-encoded and joined by dots. Once you know that, decoding one by hand takes about a minute — and it is exactly what every "JWT debugger" tool does under the hood.
The fastest path is to paste your token into the JWT Decoder, but understanding the steps makes you confident the result is correct. Let's walk through it.
The structure
A JWT has three parts separated by dots:
<header>.<payload>.<signature>
Only the first two decode to readable JSON. The third is a binary signature — you verify it, you do not read it.
Step 1 — Split on the dots
Take your token and split it into three strings at every .:
header = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
payload = eyJzdWIiOiIxMjM0IiwibmFtZSI6IkRldkJlZSIsImV4cCI6MTcxODAwODQwMH0
signature = s9Kq5x...jQ
Step 2 — Base64url-decode the header
The header is standard Base64 in its URL-safe variant. Two differences from ordinary Base64:
-is used instead of+, and_instead of/.- The
=padding is stripped.
To decode with a standard decoder, undo those changes: replace - → +, _ → /, then add = padding until the length is a multiple of 4.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
→ {"alg":"HS256","typ":"JWT"}
The alg tells you how the signature was produced (HS256 = HMAC + SHA-256). You can experiment with the same primitives in the Base64 Encoder/Decoder.
Step 3 — Base64url-decode the payload
Same routine on the middle part:
eyJzdWIiOiIxMjM0IiwibmFtZSI6IkRldkJlZSIsImV4cCI6MTcxODAwODQwMH0
→ {"sub":"1234","name":"DevBee","exp":1718008400}
This JSON is the payload — the token's claims.
Reading the common claims
| Claim | What it means |
|---|---|
iss | Issuer — who minted the token |
sub | Subject — usually the user or principal id |
aud | Audience — the intended recipient |
exp | Expiration (Unix seconds) — reject the token after this |
nbf | Not before — reject before this |
iat | Issued at |
jti | Token id |
In the example, exp: 1718008400 is a Unix timestamp. Convert it to a human date and you know exactly when the token stops being valid.
Remember: the payload is encoded, not encrypted. Anyone who reads the token off the wire can read these claims. Never place a secret here.
Step 4 — (Don't) decode the signature
The third part is a signature, not encoded JSON. Trying to Base64-decode it yields raw bytes. Its job is to let a recipient prove the header and payload were not tampered with — see our complete JWT guide for how verification works.
Decoding is not verifying
This is the trap. Decoding only requires the token itself — it tells you what the token claims. Verifying recomputes the signature with the secret (or public key) and checks exp, aud, and iss. Only a verified token should ever be trusted.
A token that decodes fine can still be forged or expired. Always verify on the server before acting on a claim. You can build and sign tokens locally with the JWT Signer to see how the signature ties everything together.
Quick recipe in JavaScript
function decodeJwt(token) {
const [header, payload] = token.split(".");
const decode = (part) => {
// Restore standard Base64 from Base64url and add padding.
const b64 = part.replace(/-/g, "+").replace(/_/g, "/");
const padded = b64 + "=".repeat((4 - (b64.length % 4)) % 4);
return JSON.parse(atob(padded));
};
return { header: decode(header), payload: decode(payload) };
}
This is exactly the logic behind the JWT Decoder — minus the UI, validation, and the client-side-only guarantee that your token never leaves the page.