BrainCoder
All guides

How to Decode a JWT (and Why Decoding Is Not Verifying)

BrainCoder4 min read

Split a JSON Web Token into header, payload and signature in your browser, read exp, nbf and iat as real dates, and see exactly where the line between decoding and verifying sits.

Decoding is not verifying

A JWT is three base64url strings joined by dots: header, payload, signature. Encoding is not encryption, so the first two are readable by anyone holding the token - there is no key involved in reading them and no secret to obtain. That is the whole of what decoding gives you: the text somebody wrote into the token.

Verifying is a different operation with a different input. Verification takes the signature, the header and the payload, plus a key the issuer published (a shared secret for HMAC, a public key for RSA or ECDSA), and recomputes the cryptographic check. A token that decodes tells you nothing about whether that check would pass, because the signature segment is deliberately opaque to a decoder and the token's author chooses its contents freely. Anyone can mint a token whose payload reads {"sub":"admin","admin":true}. So the honest label is decoded, not verified, and this page puts that on screen, in the structure report and in every answer here.

It also means this tool cannot confirm whether a token is expired for the service that issued it, whether the audience matches, or whether the issuer is who it claims to be. It shows what the token says about those things, which is useful for debugging, and it stops there. If you need a real answer, send the token to the service that issued it and let it run its own verification.

Paste it, and read the structure report

Paste a token into the field. Surrounding whitespace is removed, and a leading Bearer is stripped so that a header copied out of a request works as pasted; when either happens the page says so rather than quietly tidying your input. Input is capped at 262,144 characters (256 KB) and anything longer is refused with its real length and that limit, before a single byte is decoded.

The structure report lists every dot-separated segment with its encoded length, its decoded byte length and whether it decoded. That panel is where a bad token explains itself. A character outside the base64url alphabet is refused by name and position, which covers the three mistakes that account for nearly every failure: standard Base64 + and / where base64url expects - and _, an = padding character that base64url never uses, and a stray character pasted in from a log. The decoder also refuses a length that no base64 string can produce and a final character whose unused bits are not zero, rather than rounding them off. Segments that decode to bytes which are not valid UTF-8, or to text that is not valid JSON, are refused with that reason instead of printing replacement characters or a blank panel.

A five-segment input is recognised as a JWE - an encrypted token - and refused: decoding cannot open it without the decryption key. Anything that is not exactly three segments is refused with its real segment count. Nothing is guessed and nothing is half-decoded.

Reading the claims, and the dates that matter

The registered claims come first: iss, sub, aud, exp, nbf, iat and jti, then any private claims sorted by name. Claims the token does not carry are listed as not present, because an absent exp is very different from an exp nobody looked at.

The four NumericDate claims - exp, nbf, iat and auth_time - are seconds since the epoch, so each is shown twice: as an absolute UTC date and time, and as a reading. exp becomes expires in 2 hours or expired 3 days ago, with the countdown live rather than frozen at the moment you pasted; nbf becomes not valid for another 30 minutes or valid since 2 hours ago; iat tells you when the token was issued. If a claim arrives as a JSON string rather than a number, or as something that is not a date at all, or as a number no browser can turn into a date, the row says so instead of quietly printing a plausible-looking wrong date.

A long claim is truncated for display with its real character count and the instruction to use the copy button for all of it, and the payload being a JSON array or a bare scalar is described as what it is rather than treated as a broken claims object. None of this makes the values trustworthy: they are text from the token, and a token's author wrote every one of them.

Privacy, and the honest limits

Nothing is uploaded and no request is made with your token: decoding happens in your tab, and the page makes no network call of any kind. That is a statement about this page only. A JWT is a bearer credential - whoever holds it can present it as you - so do not paste a live token into anything, including this one, unless you are entitled to read it, and do not paste a token out of a production log.

What this tool cannot do is the list worth reading before you trust a claim: it does not verify signatures, it does not fetch a JWKS endpoint, it does not check an issuer or an audience, it cannot decrypt a JWE, and it does not follow a nested JWT inside a claim. It cannot tell you whether a token is authentic, only what it says.

Try it free — JWT Decoder

Split a JSON Web Token into header, payload and signature, decode base64url properly, and read exp, nbf and iat as real dates. Decodes only: no signature is checked, and the token never leaves your browser.

Open JWT Decoder