A JWT looks like a random blob of characters, but it is not encrypted and it is not random. It is a structured, readable token that anyone can pull apart in seconds. That surprises people the first time they paste one into a decoder and watch their user id, email, and role appear in plain text. The token was never hiding that data. It was only signing it.
This guide explains what a JWT actually contains, how to read one, and the single most important thing to understand: decoding a JWT is not the same as verifying it.
What a JWT is made of
A JSON Web Token has three parts separated by dots: header.payload.signature. Each part is base64url encoded, which is a URL-safe variant of base64 that swaps + and / for - and _ and drops the trailing = padding.
The header is a small JSON object describing how the token was signed. It usually holds two fields: alg (the signing algorithm, such as HS256 or RS256) and typ (almost always JWT).
The payload is the interesting part. It is another JSON object holding the claims: the statements the token makes about a user or session. This is where you find the subject, the expiry, and any custom data the issuer chose to include.
The signature is a cryptographic value computed over the header and payload using a secret or private key. It is what lets a server confirm the token was issued by someone who holds that key and has not been altered since.
Decoding is not verifying
Here is the part that trips up almost everyone. Decoding a JWT means base64url decoding the header and payload back into readable JSON. That requires no key, no secret, and no permission. The JWT decoder does exactly this, entirely in your browser, and so can any attacker who intercepts a token.
Verifying is a separate step. To verify, a server recomputes the signature using its own key and checks that the result matches the signature in the token. Only the party holding the key can do this. A valid signature proves two things: the token came from the expected issuer, and nobody tampered with the payload after it was signed.
So reading a JWT tells you what it claims. Verifying it tells you whether those claims can be trusted. Never confuse the two.
The standard claims
The JWT spec reserves a set of short claim names with agreed meanings. You will see these constantly:
iss(issuer): who created and signed the token.sub(subject): who the token is about, usually a user id.aud(audience): who the token is intended for.exp(expiration): a timestamp after which the token must be rejected.iat(issued at): when the token was created.nbf(not before): a timestamp before which the token is not yet valid.jti(JWT id): a unique identifier, useful for revocation and replay protection.
Timestamps are Unix seconds. A decoder shows them as raw numbers, so an exp of 1799999999 means little until you convert it. Checking exp and nbf against the current time is part of verification, not decoding.
Why you must never trust an unverified token
Because anyone can craft a JWT, a decoded payload proves nothing on its own. An attacker can build a token that claims "role": "admin", base64url encode it, and send it to your server. If your code reads the payload without checking the signature, it just handed over admin access.
Always verify the signature before you act on any claim. Reject tokens with a missing or mismatched signature, an unexpected alg, an expired exp, or an aud that is not yours. Treat the payload as untrusted input until the signature check passes.
Never put secrets in the payload
Since the payload is trivially readable, it is not a place for anything sensitive. No passwords, no API keys, no private personal data, no internal notes you would not want a user to see. Anyone holding the token can read all of it. If you need to store secret data, keep it server side and reference it by an opaque id in the token instead.
How to inspect a token
To read a JWT, paste it into the JWT decoder. It splits the token on the dots, base64url decodes the header and payload, and lays out the JSON with each claim on its own line. You will see the algorithm, the standard claims, and any custom fields in one view.
If you want to understand the encoding itself, run a single part through a base64 encoder and back. You will see how the readable JSON becomes the compact string in the token, and why anyone can reverse it. That hands-on step makes the “decoding is not verifying” point concrete: the transform is public and reversible by design.
Closing
A JWT is a signed, readable envelope, not a locked box. Decode one whenever you need to see what it claims, but remember that reading and trusting are different actions. Verify the signature, honor the expiry, keep secrets out of the payload, and you will use JWTs the way they were meant to be used.