JWT Decoder
Paste a JWT to decode and view its header, payload, and signature information. Runs locally — your token is never uploaded.
Updated July 11, 2026
100% Private & Secure
This tool runs in your browser.
Your data is never uploaded or stored.
How to use
- 1
Paste your JWT token.
- 2
The tool decodes header and payload automatically.
- 3
Review the decoded content.
When to use it
Debugging an OAuth or session token
When a request returns 401 right after login, the cause is usually a claim — an expired exp, a missing aud, or the wrong iss. Pasting the token into a decoder exposes the payload so you can read every claim without poking the server.
Verifying claims before a token goes live
If your newly minted token grants access for the wrong window, you will see it immediately in the iat (issued-at), nbf (not-before), and exp (expiry) timestamps. Converting those epoch seconds into human-readable dates catches clock-skew and TTL mistakes during development.
Inspecting a token you did not issue
Integrating with a third-party SSO provider means consuming tokens you did not create. Decoding the header reveals the alg and kid (key id), telling you which public key to fetch and which signature algorithm to expect during verification.
Checking token size before sending it on the wire
Every byte of a JWT travels in an HTTP header. A token bloated with dozens of custom claims can blow past reverse-proxy or CDN header limits (commonly 8 KB). Decoding shows exactly which claims are inflating the payload.
How it works
The three-part structure: header.payload.signature
A JWT is defined by RFC 7519 as exactly three Base64url-encoded segments separated by dots: header.payload.signature. The header declares the token type and signing algorithm (e.g. {"alg":"HS256","typ":"JWT"}). The payload carries the claims — statements about the subject, such as sub, exp, and admin: true. The signature is computed by the JWS rules in RFC 7515 over the bytes base64url(header) + "." + base64url(payload).
The signature is not there to hide the payload — it is there to prove the payload was not tampered with. Anyone can decode a JWT; only the holder of the secret key can produce a valid one.
- Header (
alg,typ, optionalkid) — which algorithm and key were used - Payload — registered claims like
iss,sub,aud,exp,nbf,iat, plus any custom claims - Signature — HMAC or digital signature over the first two segments, itself Base64url-encoded
Base64url is not Base64
JWT segments use Base64url encoding, a variant of Base64 defined in RFC 7515. Two substitutions and a trim make it URL-safe: + becomes -, / becomes _, and any trailing = padding is removed. This matters because tokens routinely travel in URLs, cookies, and headers where + and / would be misinterpreted or double-encoded.
If you decode a segment with a plain Base64 decoder, it usually still works — until a payload contains a byte that maps to + or /, at which point decoding silently corrupts the JSON. Always convert Base64url back to standard Base64 (and re-add padding) before decoding.
// Base64url -> standard Base64, for safe atob()
function b64urlDecode(s) {
s = s.replace(/-/g, '+').replace(/_/g, '/');
while (s.length % 4) s += '=';
return decodeURIComponent(escape(atob(s)));
} Decoding is not verification — the alg=none trap
A decoder never checks the signature, so a token's claims can be read but never trusted on their own. The canonical attack is "alg":"none": an attacker edits the header to declare the unsigned algorithm, strips the signature, and modifies the payload (e.g. "role":"admin"). If the server blindly trusts the header and skips signature verification, the forged token is accepted and authentication is fully bypassed.
This is why RFC 7519 is layered on RFC 7515 (JWS) and, for asymmetric keys, RFC 7517 (JWK). Defense is an explicit allowlist: pass the expected algorithms (e.g. ['RS256']) to your verifier, reject none, and verify exp, nbf, iss, and aud on every request.
Common mistakes & edge cases
Token decodes to garbage or throws on JSON.parse
You are decoding a Base64url segment with a standard Base64 decoder. Replace - with +, _ with /, and pad with = to a multiple of 4 before decoding.
A hand-edited token is accepted by the server
Your verifier is not enforcing an algorithm allowlist. Pass algorithms explicitly (e.g. jwt.verify(token, key, { algorithms: ['RS256'] })) and reject "none". Never omit the algorithms option.
Token rejected with "token expired" immediately after issuing
Clock skew between issuer and verifier, or exp is being set in local time instead of UTC. Always compute exp as now_in_seconds + ttl, and configure a small leeway (a few seconds) on the verifier.
A long token breaks routing or returns 400 from a proxy
The payload is too large. Move bulky claims out of the token (store a reference id, fetch details server-side) so the header stays well under proxy limits.