Security · 3 min read ·

What’s inside a JWT, and why decoding isn’t verifying

A JSON Web Token looks like random characters, but anyone can read it. Here is what its three parts hold, and the mistake that leads to security holes.

A JWT (JSON Web Token) is the long string that keeps you logged in to many web apps and APIs. It looks encrypted, but in most cases it is not: anyone who has the token can read what is inside.

Three parts, separated by dots

eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0MiJ9.3q2-7w…
└ header ──────────┘ └ payload ───────┘ └ signature

The header says how the token is signed, for example HS256 or RS256. The payload holds the claims: who the user is (sub), when the token was issued (iat) and when it expires (exp), plus whatever the app adds, such as roles. Both are plain JSON encoded with base64url, which is an encoding, not encryption.

The signature is what matters

The third part is a signature over the first two, made with a secret or a private key. Only a server with the right key can check it. If anyone changes a single character of the payload, say the role from user to admin, the signature no longer matches.

Decoding is not verifying

A token that decodes cleanly tells you what it claims, not whether the claims are true. Your backend must verify the signature with the key, reject tokens whose exp has passed and accept only the algorithms it expects, never “none”. JWT libraries do this for you; skipping it is a classic vulnerability.

Practical rules

Don’t put secrets such as passwords in the payload, because it is readable. Keep expiry times short. Treat a token like a password while it is valid: anyone who copies it can use it until it expires.

Where to keep a token in the browser

Anything stored in localStorage can be read by every script on the page, so a single cross-site scripting (XSS) bug can leak the token. An HttpOnly cookie can’t be read by JavaScript at all, which removes that risk; combine it with the Secure and SameSite flags and protection against cross-site request forgery. There is no perfect option, but for session tokens HttpOnly cookies are the safer default.

Expiry and refresh tokens

A JWT can’t be “logged out” on its own: it stays valid until its exp time unless the server keeps a revocation list. That is why access tokens usually live for minutes, not days. A longer-lived refresh token, kept more carefully and checked against the server, is used to get a new access token when the old one expires.

Common claims at a glance

iss  issuer     who created the token
sub  subject    the user or client it is about
aud  audience   which service should accept it
exp  expires    Unix time after which it is invalid
nbf  not before Unix time before which it is invalid
iat  issued at  Unix time when it was created

The time claims are Unix timestamps in seconds. A server must check aud as well as exp: a valid token issued for one service should not be accepted by another.

Reading a token

The 4Dev JWT Decoder splits a token into its parts and shows the header and payload as readable JSON. It only decodes and does not check the signature. The token is sent to the 4Dev server to be decoded and is not logged or stored, but avoid pasting live production tokens, and revoke any token you think was exposed.

Open the JWT Decoder

LinkedInXFacebook