What Is a JWT?

Updated July 27, 2026

A JWT (JSON Web Token, usually pronounced “jot”) is a compact way to package a set of claims — like “this user is logged in as #1234, and this token expires at 3pm” — into a string that can be passed around and cryptographically verified without a database lookup.

What’s actually inside one

A JWT is three parts joined by dots: header.payload.signature. The header and payload are each just a JSON object, base64url-encoded (a URL-safe variant of Base64). That means the header and payload are not encrypted — anyone can decode them and read the contents instantly. Only the signature is cryptographic.

Here’s a real one, wrapped for readability:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsZXggTGVlIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Decode the first segment and you get the header:

{ "alg": "HS256", "typ": "JWT" }

The second gives the payload:

{ "sub": "1234567890", "name": "Alex Lee", "iat": 1516239022 }

Notice how little effort that took. No key, no tooling, no permission — just Base64. That is the single most important thing to understand about JWTs.

The claims you’ll see everywhere

Some claim names are standardised by RFC 7519, the specification that defines JWTs, and you’ll meet them in almost every token:

Claim Name Means
iss Issuer Who created the token
sub Subject Who the token is about, usually a user ID
aud Audience Who the token is intended for
exp Expiration Unix timestamp after which it’s invalid
nbf Not before Unix timestamp before which it’s invalid
iat Issued at Unix timestamp of creation
jti JWT ID Unique identifier, used for revocation lists

exp, iat, and nbf are Unix timestamps — seconds since 1970, not milliseconds. Passing JavaScript’s Date.now() straight into exp produces a token that expires roughly fifty thousand years from now, which is a genuinely common bug.

Everything else is a custom claim the issuing system chose to include: roles, permissions, a tenant ID, whatever the application needs.

The one rule that matters most

Never put sensitive data in the payload. Because the payload is only encoded, not encrypted, anything you put there — a password, an API key, a full name someone expected to be private — is readable by anyone who has the token, including it sitting in browser storage or a network log. JWTs are for claims a client is allowed to see, not for hiding secrets.

The signature protects integrity, not confidentiality. It guarantees nobody altered the payload. It does nothing to stop them reading it.

Why servers still trust them

The signature is what matters. A server that issued the token (or shares its secret key) can recompute the signature from the header and payload and check it matches. If it does, the server knows the payload hasn’t been altered since it was issued — without needing to look anything up in a database. That’s the main appeal of JWTs over traditional session IDs: they’re self-contained and verifiable offline.

There are two families of signing algorithm:

If you’re standing up an auth system that more than one service will validate against, reach for RS256. Sharing an HMAC secret across a dozen services means a dozen places it can leak from, and any one of them can then mint valid tokens.

The alg: none trap

Early in the JWT spec’s life, "alg": "none" was a legal header meaning “this token is unsigned.” Libraries that honoured it would accept a token with an empty signature as valid — so an attacker could take a real token, rewrite the payload to "role": "admin", set the algorithm to none, strip the signature, and walk in.

A related attack swaps RS256 for HS256 and signs with the public key. A naive library verifies using whatever algorithm the header claims, and the public key is, by definition, public.

Modern libraries defend against both, but the lesson generalises: never let the token tell you how to verify it. Your server should decide the expected algorithm and reject anything else, rather than trusting a field an attacker controls.

Where to store one in a browser

There’s no unambiguously correct answer, only trade-offs.

localStorage is easy and survives page reloads, but any JavaScript running on your page can read it — so a single XSS bug hands over every user’s token. An httpOnly cookie can’t be read by JavaScript at all, which removes that risk, but introduces CSRF exposure that needs the SameSite attribute and possibly a CSRF token to close.

The prevailing advice is httpOnly, Secure, SameSite=Lax cookies for session tokens. It trades a problem with no in-page mitigation (XSS reading storage) for one with well-understood mitigations.

Revocation: the real trade-off

The self-contained property that makes JWTs appealing is also their sharpest edge. A traditional session ID means a database lookup on every request, which is a cost — but it also means deleting the row logs the user out instantly.

A signed JWT is valid until it expires, full stop. There is no lookup to fail. If a user logs out, changes their password, or has their account disabled, any already-issued token keeps working until exp passes.

The usual mitigations are short-lived access tokens (5–15 minutes) paired with a longer-lived refresh token that is checked against a database, or a denylist of revoked jti values — which quietly reintroduces the lookup JWTs were meant to avoid. Worth knowing before choosing JWTs for sessions: if you need instant revocation, plain server-side sessions may simply be a better fit.

Decoding one yourself

JWT Decoder splits a token into its header and payload and shows both as readable JSON — useful for debugging an auth flow or just understanding what a token actually contains. It deliberately never attempts signature verification, since that needs the issuer’s secret key, which a client-side tool never has and shouldn’t.

Since the underlying encoding is just base64url, Base64 Decode and a JSON Formatter can also get you there manually if you want to see each step — a genuinely useful exercise the first time, because doing it by hand makes the “the payload is not secret” point permanently obvious. If you’re implementing JWT signing yourself, our SHA-256 Hash Generator is handy for understanding the hashing primitives HMAC-based JWT algorithms build on.

Share