If you've logged into a website and stayed logged in as you clicked around, there's a good chance a JWT (JSON Web Token, usually pronounced "jot") was quietly doing the work behind the scenes. They're everywhere in modern authentication, and understanding what's actually inside one demystifies a lot of how login sessions work.
What a JWT actually is
A JWT is a compact way to package a set of claims (statements about a user, like "this is user 42" or "this token expires at 3pm") into a string that can be safely passed around, and — this is the important part — cryptographically verified as untampered without needing to check back with a database every single time.
A JWT looks like three chunks of text separated by dots:
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6IkFsaWNlIn0.4Q9z3lZ...
Those three parts are, in order: the header, the payload, and the signature.
Part 1: the header
Base64URL-decode the first chunk and you get a small JSON object:
{
"alg": "HS256",
"typ": "JWT"
}
This just says which algorithm was used to sign the token (HS256 here, a common HMAC-based
option) and that it's a JWT. Nothing secret or interesting yet.
Part 2: the payload
Decode the second chunk and you get the actual claims:
{
"sub": "1234",
"name": "Alice",
"iat": 1712345678,
"exp": 1712349278
}
A few claim names show up constantly, standardized by the JWT spec itself:
sub(subject) — who this token is about, usually a user ID.iat(issued at) — a Unix timestamp of when the token was created.exp(expiration) — a Unix timestamp after which the token is no longer valid.iss(issuer) — who created and signed the token.aud(audience) — who the token is intended for.
Anything else — name, role, permissions — is a custom claim, added by whoever's issuing the tokens for their application's needs.
The part everyone gets wrong at first: the payload isn't secret
This is the single most important thing to understand about JWTs: the header and payload are just Base64URL-encoded, not encrypted. Encoding is not encryption — anyone who has the token can decode the payload and read every claim inside it in about two seconds, with no key needed at all. Never put a password, a credit card number, or anything genuinely secret inside a JWT payload. It's meant to be read, just not undetectably modified.
Part 3: the signature, and why it matters
The signature is what makes a JWT actually useful for security. It's created by taking the header and payload, and running them through a signing algorithm together with a secret key that only the server knows. When the server later receives the token back, it recomputes the signature from the header and payload it received, and checks whether that matches the signature attached to the token.
If even one character of the payload was changed — say, someone tried to edit "role": "user"
into "role": "admin" — the recomputed signature won't match, because the signature is
mathematically tied to the exact original content. The server rejects the token. This is what lets a server
trust a token's claims without needing to look anything up in a database: if the signature checks out, the
claims haven't been tampered with since the server itself issued them.
Why this makes JWTs good for stateless authentication
Traditional session-based login stores session data on the server (often in a database or cache) and gives the browser just a session ID to reference it. Every request means a lookup. A JWT flips this: the client holds the actual claims, and the server just verifies the signature — no database round-trip required to check who's making the request. This is a big part of why JWTs are popular for APIs with many independent services, since any service holding the same secret key can verify a token without sharing a session store.
The trade-off: you can't easily un-issue a JWT
That stateless design cuts both ways. Since the server doesn't track which tokens it's issued, there's no simple built-in way to revoke one early — if you want to force a user to log out immediately (say, after a password change or a suspected account compromise), a valid, unexpired JWT can't just be deleted from a database like a traditional session can. Common workarounds include keeping tokens short-lived (a few minutes to an hour) paired with a separate refresh mechanism, or maintaining a server-side denylist of specific revoked tokens — which reintroduces some of the server-side state JWTs were meant to avoid.
What "decoding" a JWT does and doesn't tell you
Decoding a JWT (reading its header and payload) tells you what claims it contains. It does not tell you whether those claims are trustworthy — that requires actually verifying the signature against the correct secret or public key, which only the systems that share that key can do. A tool that just decodes a JWT for you to inspect is genuinely useful for debugging, but it is not, and cannot be, proof that the token is authentic.
A quick mental model
Think of a JWT less like a locked box and more like a signed, tamper-evident letter written on clear glass. Anyone can read what's written on it. But if anyone other than the original signer tries to alter the message, the seal visibly breaks. That's the entire security model in one sentence.