How to Decode a JWT (and Why Decoding Isn't Verifying)
A JWT looks opaque — eyJhbGciOi… — but it's just two Base64url-encoded JSON objects and a signature, joined by dots. Decoding takes seconds and requires no secrets. Verifying is an entirely different operation, and confusing the two is a security bug.
In short
Decode a JWT by splitting it on its two dots into header, payload, and signature, then Base64url-decoding the first two parts into JSON — no secret key required, because decoding is not verifying. The header names the signing algorithm; the payload carries claims like sub, iss, and exp. Only a server holding the signing key can verify a JWT; clients that trust decoded claims without verification create an authentication bypass.
The anatomy: header.payload.signature
Split on the dots. Part one (header) declares the signing algorithm and token type. Part two (payload) carries the claims — who the token is about (sub), who issued it (iss), when it expires (exp), plus whatever custom data the issuer added. Part three is a cryptographic signature over the first two parts: binary, not meaningfully human-readable, and the only part that involves a secret.
// header (decoded)
{ "alg": "HS256", "typ": "JWT" }
// payload (decoded)
{ "sub": "user-7", "name": "Ada", "iat": 1780560000, "exp": 1780563600 }Decoding by hand: the Base64url detail
JWTs use Base64url, not plain Base64: - replaces +, _ replaces /, and padding is dropped. Feed a section to a strict Base64 decoder unmodified and it may reject it — swap the characters and restore padding first. In JavaScript, atob handles it after the substitution:
function decodeJwtPart(part) {
const b64 = part.replace(/-/g, "+").replace(/_/g, "/")
.padEnd(part.length + (4 - part.length % 4) % 4, "=");
return JSON.parse(new TextDecoder().decode(
Uint8Array.from(atob(b64), c => c.charCodeAt(0))
));
}
const [h, p] = token.split(".");
console.log(decodeJwtPart(h), decodeJwtPart(p));Decoding without code
For day-to-day debugging — 'what's IN this token my API just received?' — paste a section into our Base64 decoder and read the pretty-printed claims immediately. Because it runs entirely client-side, the token never leaves your machine — which matters, since pasting live production tokens into arbitrary websites hands out working credentials. Prefer expired or development tokens anywhere online, ours included; with client-side tools it's defense in depth rather than necessity.
Decoding ≠ verifying — the part that bites
Anyone can decode a JWT; that's by design — it's encoding, not encryption. What anyone CANNOT do without the key is produce a valid signature. So the rule: clients may decode (to read expiry, show a username); only servers verify — checking the signature against the secret or public key, validating exp/iss/aud, and rejecting alg tricks. Code that trusts decoded claims without verification is an authentication bypass waiting for its CVE. If claims must be secret rather than just tamper-proof, JWT alone is the wrong tool — that's what JWE (encryption) is for.
Sources
Frequently asked questions
Is it safe that anyone can read my JWT's payload?
It's the design: JWTs guarantee integrity (tamper-evidence via the signature), not confidentiality. Never put secrets — passwords, PII you wouldn't expose — in claims. If you can see it in a decoder, so can anyone who obtains the token.
Why does my Base64 decoder reject a JWT section?
Base64url vs Base64: JWTs use - and _ instead of + and /, with padding stripped. Translate those characters and re-pad to a multiple of 4 with =, and any decoder accepts it.
Can I check whether a JWT is expired by decoding it?
Yes — the exp claim is a Unix timestamp; compare it to now. That's a legitimate client-side decode use (skip a doomed API call). Just remember the server must still verify: an attacker can rewrite exp in an unverified token trivially.
Paste a JWT header or payload section into Base64 to JSON — decoded and pretty-printed instantly, 100% in your browser.
Decode one nowKeep working
Related tools
JWT Decoder
Paste a token, read it like a human: decoded header and payload, annotated claims with local timestamps, expiry at a glance — plus optional signature verification.
Base64 to JSON
Decode Base64 payloads straight to validated, pretty JSON — or encode JSON to Base64 for headers and env vars.
JSON Formatter
Format, beautify, and validate JSON instantly in a professional VS Code-style editor — free, fast, and 100% private.
JSON Viewer
Explore any JSON document in an interactive, collapsible tree — keys, types, and counts at a glance, with code view one click away.
URL Encode / Decode
Percent-encoding both ways — make any text safe in a URL, or turn %20-soup back into readable text.
Hash Generator
Every common digest at once — type text or drop a file and read MD5 through SHA-512 side by side, with HMAC mode and one-paste checksum verification.