Skip to content
Coding Security

What's Inside a JWT

By ToolzHive · · 4 min read

A JWT (JSON Web Token) is the long string of gibberish your app stores after you log in. It is not encrypted and not magic — it is three Base64 chunks with a signature. Knowing what is inside helps you use them correctly and avoid the classic security mistakes.

Three parts, separated by dots

Every JWT has the same shape: header.payload.signature. The two dots split it into three Base64URL-encoded sections.

  • Header — a tiny JSON object naming the signing algorithm (for example HS256 or RS256) and the token type.
  • Payload — the claims: the actual data, such as the user ID, roles, and an expiry time.
  • Signature — a cryptographic stamp over the header and payload, proving the token has not been altered.

Because the first two parts are only Base64-encoded, not encrypted, anyone can read them. Decode a JWT and you see the payload in plain JSON. This is the single most important fact about JWTs.

What the payload holds

Claims come in two kinds. Registered claims are standard short keys with agreed meanings:

  • sub — the subject (usually the user ID).
  • iat — issued-at time.
  • exp — expiry time. After this, the token should be rejected.
  • iss and aud — who issued the token and who it is for.

Alongside these you can add your own custom claims, like a username or permission list. Keep them small — the token travels on every request, and it is readable, so it is no place for secrets.

How the signature protects it

The signature is what makes a JWT trustworthy. The server combines the header, the payload and a secret key (or a private key) and runs them through the signing algorithm. On each request, the server recomputes the signature and checks it matches.

Change a single character of the payload — bump your own role from "user" to "admin" — and the signature no longer matches, so the server rejects it. You cannot forge a valid token without the secret. This is why the token can be readable yet still safe: readability is fine, tamperability is not, and the signature blocks tampering.

The classic mistakes

  • Treating the payload as secret. It is not. Never put passwords, card numbers or anything private in a JWT. Assume the user can read every claim.
  • Not checking expiry. A token without an enforced exp, or a server that ignores it, is a token that lives forever. Always validate exp.
  • The alg: none trap. Early libraries allowed an algorithm of "none", meaning no signature. A careless server would accept it, letting anyone forge tokens. Always pin the expected algorithm server-side.
  • Weak secrets. An HS256 token is only as strong as its shared secret. A short, guessable secret can be brute-forced offline, after which anyone can mint valid tokens. Use a long, random secret.

Readable is not the same as verified

A key habit: decoding a JWT tells you what it claims, not whether it is valid. Decoding just reverses the Base64. Verification means recomputing the signature with the secret and checking the expiry. A browser-based decoder is perfect for inspecting a token during development — seeing the claims, confirming the expiry, checking the algorithm — but the real validation always happens on the server that holds the key.

So when you are debugging an auth flow and a request is being rejected, decode the token first: is it expired? Is the sub what you expect? Is the algorithm the one your server expects? Inspecting the header and payload, and building a test token to try against your endpoint, both run locally in your browser, so you can take apart and assemble JWTs while developing without sending a live session token to anyone else.

Tools mentioned in this guide

Accessibility Menu

Personalize your experience. Open with Ctrl+U (Option+U on Mac). Preferences stay in this browser when storage is available.

Move / Hide Widget

Restore at any time with Ctrl+U (Option+U on Mac).

Accessibility statement and help