How to decode and verify a JWT
A JSON Web Token looks like noise, but two of its three parts are plain JSON that anyone can read. Decoding tells you what a token claims. Only verifying the signature tells you whether to believe it.
A JWT is a small signed document. It carries a set of claims, such as who the user is and when the token expires, plus a signature that lets the receiver check those claims came from someone it trusts. The claims are readable by anyone. The signature is what makes them believable.
Three parts, two of them readable
A JWT is three pieces of base64url-encoded data joined by dots: a header, a payload and a signature. Decoded, a typical token looks like this:
header {"alg":"HS256","typ":"JWT"}
payload {"sub":"1234567890","name":"Jane Doe","iat":1767225600}
signature an HMAC-SHA256 of the first two parts, keyed with a secret
Base64url is an encoding, not encryption. It exists to make binary data safe to put in a URL or a header, and it can be reversed by anyone without a key. That is why the JWT decoder can show you the header and payload the moment you paste a token, and why a JWT payload should never contain anything its holder is not allowed to see.
The claims worth reading
The payload can hold anything, but a handful of registered claims come up constantly:
- iss, the issuer: who created the token.
- sub, the subject: usually the user or client the token is about.
- aud, the audience: which service the token is meant for.
- exp, the expiry time, after which the token must be refused.
- nbf, not before: the token is invalid until this time.
- iat, issued at: when the token was created.
- jti, a unique id for the token, useful for spotting replays.
Times are whole seconds since the start of 1970, which is hard to read by eye. The decoder shows them as dates and tells you whether the token has expired. The header often carries a kid, a key id that says which key signed the token.
Decoding is not verifying
Because the header and payload are only encoded, anyone can write a token claiming to be any user with any permissions. Decoding it will happily show you those claims. The signature is the only thing that ties the token to an issuer, and checking it is the only thing that makes the claims worth acting on.
Front-end code can decode a token to display a name or an expiry time. Decisions about access belong on the server, after verification.
How verification works
There are two families of algorithm, and they answer different questions.
- HMAC (HS256, HS384, HS512) uses one shared secret to both sign and verify. It is fast and simple, but anything that can verify a token can also create one, so it suits a single service that issues and checks its own tokens.
- Public key algorithms sign with a private key and verify with a public one: RSA (RS256 to RS512), RSA-PSS (PS256 to PS512), ECDSA (ES256 to ES512) and EdDSA with Ed25519 keys. Many services can verify without being able to issue, which is why identity providers use them. The public keys are usually published as a JWK set, and the token's kid picks the right one.
The decoder accepts keys as PEM, an X.509 certificate, a single JWK or a JWK set, and verifies with your browser's own Web Crypto, so neither the token nor the key leaves the page.
Three mistakes servers still make
- Accepting alg none. An unsigned token proves nothing. The decoder flags it, and a server must refuse it outright.
- Letting the token choose the algorithm. If a server reads the algorithm from the token's own header, an attacker can switch an RSA token to HMAC and sign it with the RSA public key as the secret, which the server then treats as valid. Configure each key with the one algorithm it is meant for.
- Checking the signature and nothing else. A validly signed token can still be expired, not yet valid, or meant for a different service. Check exp, nbf, aud and iss every time, with a small allowance of a minute or so for clock differences.
Why a signature fails
When a token you expected to verify does not, it is nearly always one of these:
- The wrong key, often the secret or public key from another environment.
- A rotated key. Compare the kid in the header with the key you are using.
- An encoded secret. Some systems store HMAC secrets base64url-encoded. The decoder has a switch for this.
- A damaged token. A character lost or a line break added while copying is enough to break the signature.
Signing test tokens
The encoder works the other way round: edit the header and payload as JSON, set iat to now and exp an hour ahead with a click each, and sign. It can generate a fresh HMAC secret or key pair in the browser, which is exactly what you want for tests. Never sign test tokens with a production key, and never paste a production private key into any tool you did not build.
Handle tokens like passwords
Most JWTs are bearer tokens: whoever holds one can use it until it expires, no questions asked. Treat them accordingly.
- Do not paste live tokens into tools that send them to a server.
- Do not log whole tokens. Log the jti or the subject instead.
- When someone genuinely needs a token, send it as a one-time link rather than a chat message, where it would stay readable long after it should have expired. The decoder can hand a token straight to one.
Common questions
Is a JWT encrypted?
Almost never. The usual kind of JWT is signed, not encrypted: its header and payload are base64url-encoded JSON that anyone holding the token can read. An encrypted variant exists but is rarely used. Never put anything in a JWT payload that the holder should not see.
How do I decode a JWT?
Split it at the two dots and base64url-decode the first two parts, which gives you the header and the payload as JSON. The third part is the signature, which is binary and only meaningful when checked against a key. A decoder that runs in your browser does this as you paste.
What is the difference between decoding and verifying a JWT?
Decoding only reads the token, and anyone can do it. Verifying checks the signature against a key you trust, which proves the token was issued by the holder of that key and has not been changed since. Act on the claims only after verifying.
Why is my JWT signature invalid?
Usually the key is wrong: a secret from another environment, a public key that was rotated after the token was issued, or a key that does not match the kid in the header. For HMAC tokens, check whether the secret is stored base64url-encoded. A token damaged while copying, with a missing character or an added line break, also fails.
What does alg none mean in a JWT?
It means the token is unsigned, so anyone could have written everything in it. A server must never accept it. Configure your library with the exact algorithms you expect, so a token cannot choose a weaker one for itself.
Is it safe to paste a JWT into an online decoder?
Only if the decoder works entirely in your browser and sends nothing to a server. A live JWT is a credential: whoever holds it can use it until it expires. Even with a local decoder, prefer tokens from a test environment, and never paste a production signing key anywhere.