How to Decode a JWT Safely
The three parts of a JWT
A signed JWT is header.payload.signature, each Base64URL-encoded. The header names the algorithm (alg) and key id (kid), the payload carries claims, and the signature proves the token was issued by someone holding the key.
Claims you should always check
exp— expiry as a Unix timestamp; reject expired tokensiat— issued-at time, useful for detecting clock skewnbf— not valid before this momentiss— the issuer your server must whitelistaud— the audience the token was minted forsub— the subject, usually the user id
Decoding is not verifying
Anyone can rewrite a payload and re-encode it. Only signature verification with the shared secret or the issuer's public key (JWKS) makes claims trustworthy — and that must happen server-side. Never grant access based on a decoded payload in the browser.
Debug a token without leaking it
Paste the token into the JWT Decoder: it prints the header and payload as formatted JSON and tells you whether the token has expired. Decoding runs locally in your browser with no network request. To convert raw exp values by hand, use the Unix Timestamp Converter.
Reading a token by hand
A JWT is three Base64URL segments separated by dots. Split on the dots, Base64URL-decode the first two, and you have two JSON documents. The third segment is binary signature material and will look like noise — that is expected.
Base64URL differs from standard Base64: + becomes -, / becomes _, and padding is dropped. A decoder that does not account for this fails on roughly a quarter of tokens, which is why generic Base64 tools sometimes produce truncated output.
Verification, step by step
Trustworthy verification on the server always performs the same checks in the same order. Skipping any one of them has caused real breaches:
- Reject the token unless
algis on your explicit allowlist — never trust the algorithm the token names for itself. - Fetch the issuer's public key by
kidfrom a cached JWKS endpoint, or load the shared secret for HMAC tokens. - Verify the signature over the exact
header.payloadbytes received. - Check
expandnbfwith a small clock-skew tolerance, typically 30–60 seconds. - Check
issandaudmatch the values your service expects. - Only then read the claims and act on them.
Classic JWT vulnerabilities
The alg: none attack strips the signature and sets the algorithm to none; libraries that honour it accept forged tokens. The algorithm-confusion attack takes an RS256 public key, which is not secret, and submits an HS256 token signed with that public key as the HMAC secret — servers that pick the algorithm from the token accept it.
Both are defeated by the same rule: the server decides the algorithm and the key, the token never does.
Access tokens, refresh tokens and revocation
A signed JWT is valid until it expires and cannot be recalled, which is the fundamental trade-off of stateless auth. Keep access tokens short-lived (5–15 minutes) so the damage window from a leak is small, and keep the refresh token in server-side storage where it can be revoked and rotated on every use.
If you need instant revocation of access tokens, you need server state — a deny-list keyed by token id (jti), checked on every request. At that point be honest about whether opaque session tokens would serve you better.
What belongs in a payload
Payloads are readable by anyone holding the token, so they must never contain passwords, API keys, full personal records or anything covered by data-protection rules. Put a user id, a role or scope list, and the standard time and audience claims — nothing else.
Size matters too: tokens travel on every request, often in a header with a practical limit around 8 KB. Embedding a large permission matrix in the payload slows every single call your users make.
Common JWT problems
A token that works locally but fails in production usually means a different signing key or issuer per environment. alg: none tokens must always be rejected. If the payload is unreadable, you likely have an encrypted JWE rather than a signed JWS.
Frequently asked questions
How long should an access token live?
Commonly 5–15 minutes, paired with a longer-lived refresh token that can be revoked.
Should JWTs be stored in localStorage?
An httpOnly, secure cookie is safer because JavaScript — and therefore XSS — cannot read it.
Can I trust claims after decoding a token in the browser?
No. Decoding proves nothing. Only server-side signature verification makes claims trustworthy.
What is the kid header for?
It identifies which key from the issuer's JWKS signed the token, so issuers can rotate keys without invalidating everything at once.
Why is my payload unreadable after decoding?
You likely have an encrypted JWE (five segments) rather than a signed JWS (three segments); JWE requires the decryption key.