Skip to content
Control Plane Labs

JWT Decoder

Decode a JWT header and payload. Optionally verify HS256/RS256 signatures — all client-side.

Privacy: The token never leaves your browser. Decoding is base64url plus JSON.parse in this tab, and the optional HS256/384/512 check runs through the Web Crypto API locally — neither the token nor the secret is sent to a server or logged. The one caveat you control: if you use the shareable link, the token is in the URL, so treat that link as being as sensitive as the token itself. The secret is never written to the URL.

Decoding happens entirely in this tab. The token is never uploaded, and a JWT payload is only base64url — it is readable by anyone who has the token, signed or not.

What it does

Paste a JWT and get the header and payload pretty-printed, every registered claim explained in plain English, the timestamps converted to real dates with a verdict on whether the token is currently valid, and a list of the things about the header that should worry you. If the algorithm is HS256, HS384, or HS512 you can also paste the shared secret and check the signature.

A leading Bearer is stripped, so you can paste straight from an Authorization header. Five-part tokens are recognised as JWE and reported as such rather than failing with a confusing parse error.

How it works

Everything runs in the tab. The three dot-separated segments are base64url — that is + and / swapped for - and _ with the padding dropped, per RFC 7515 Appendix C. Decoding is atob on a re-padded string, then TextDecoder and JSON.parse. No network request is made and nothing is stored.

Signature verification uses the Web Crypto API: the secret is imported as a raw HMAC key and crypto.subtle.verify compares the signature against the signing input, which is the first two segments joined by a dot — the exact bytes off the wire, not a re-serialisation. That matters, because JSON does not round-trip byte-for-byte, and verifying a re-encoded payload would produce false failures.

What the warnings are actually about

The header is where JWT implementations get broken, and the tool flags four things.

alg: none means the token is unsigned. A verifier that reads the algorithm out of the token’s own header and honours none accepts anything. RFC 8725 §3.2 — the JWT Best Current Practices document — says reject it outright.

Any HS* algorithm draws a warning for the algorithm-confusion attack. HMAC uses one key for both signing and verifying; RSA and ECDSA use a private key to sign and a public key to verify. If your service publishes an RS256 public key and the verifier picks its algorithm from the header, an attacker takes that public key, re-signs a forged payload as HS256 using the key material as the HMAC secret, and your verifier happily validates it. The fix is one line: pin the expected algorithm in your verification call instead of trusting the token.

jku and jwk headers tell the verifier where to find the key, or hand it the key directly. Trusting either lets the token choose the key that validates it, which is the same failure in a different costume — RFC 8725 §3.8 covers it.

A crit header lists extensions the verifier must understand. If it does not understand one, the correct behaviour is to reject the token, not to skip it.

On the payload side the most common real-world bug is time units. RFC 7519 §2 defines NumericDate as seconds since the Unix epoch, but Date.now() returns milliseconds. A token signed with Date.now() + 3600000 expires roughly 48000 years from now and every verifier accepts it forever. Anything above 1e11 gets flagged. A missing exp gets flagged too: without it the token is valid until your server-side revocation list says otherwise, and most deployments do not have one.

When to reach for it

Reach for it when a request is coming back 401 and you need to know whether the token expired, whether the aud matches what the API expects, or whether the iss is the environment you think it is. Reach for it when you are handed a token from a system you do not own and want to see what identity and scopes it actually asserts. Reach for it when a signature check fails locally and you want to confirm whether the secret is wrong or the token is corrupted — a valid decode with a failed HMAC narrows that down immediately.

Do not use it as a verifier for anything that matters. A green result here means the bytes match a secret you pasted; it does not check the issuer, the audience, the clock skew policy, or your revocation list. Real verification belongs in a library, server-side, with the algorithm pinned.

And remember what a decoded payload proves: nothing about confidentiality. A JWS payload is encoded, not encrypted. Anyone who intercepts the token reads every claim in it.

Frequently asked questions

Is it safe to paste a production token here?+
The decoding is local — no network request is made, and the page works offline. Still, a JWT is a bearer credential: anyone holding it can act as you until it expires. The real risks are the ones around the tool, not inside it: browser extensions can read page content, the token lands in your clipboard history, and the shareable link puts it in the URL bar and your browser history. For a live production token, prefer an expired copy or one from staging.
Why can I read the payload without a key?+
A JWS is signed, not encrypted. The header and payload are plain base64url — an encoding, not a cipher — so any holder of the token can read every claim. The signature only proves the token has not been altered since it was signed. Never put anything in a JWT you would not put in a log line. If you need confidentiality, you want a JWE (five dot-separated parts instead of three), whose payload is encrypted and which this tool cannot decode.
What is wrong with alg=none?+
It declares the token unsigned. A verifier that reads the algorithm from the token's own header and honours none will accept any payload an attacker cares to write, which is a complete authentication bypass. RFC 8725 §3.2 says to reject it. The general rule: the algorithm is a property of your key, decided by your configuration — not a field the token gets to choose.
Why does the tool only verify HMAC signatures?+
HS256/384/512 need one shared secret you can paste. RS, PS, ES, and EdDSA are verified with the issuer's public key, which normally means fetching a JWKS document from the issuer's /.well-known/ endpoint and selecting a key by kid. Doing that here would mean making network requests on your behalf, which defeats the point of a browser-only tool. For those algorithms, verify server-side with a library that pins both the expected algorithm and the key source.
The exp claim looks like a date far in the future. What happened?+
Almost always a milliseconds bug. RFC 7519 defines NumericDate as seconds since the Unix epoch, but Date.now() in JavaScript returns milliseconds. Signing with Date.now() + 3600000 instead of Math.floor(Date.now()/1000) + 3600 produces a token that expires in the year 50000. The tool flags any time claim above 1e11 for this reason.

→ Read the full guide:How this tool actually works