What a JWT actually is
A JSON Web Token (pronounced "jot," and defined by RFC 7519) is a compact, URL-safe way to package a set of claims — statements about a user or session — into a single signed string. Its main job is stateless authentication: instead of the server keeping a session record in memory or a database, it hands the client a token that contains the identity information, signed so it can't be faked. On each request the client sends the token back (usually in an Authorization: Bearer <token> header), and the server simply verifies the signature rather than looking anything up.
Every JWT is three Base64URL-encoded parts joined by dots:
header.payload.signature — for example eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0In0.dQw4w9WgXc. The dots separate the three sections; each section is independently decodable.The three parts
The header describes the token itself: which signing algorithm was used (alg) and the token type (typ). Decoded, a typical header is just:
{ "alg": "HS256", "typ": "JWT" }The payload carries the claims. Some are registered — standard short names defined by the spec, like iss (issuer), exp (expiry), and sub (subject) — and you're free to add your own custom claims alongside them. A decoded payload might look like:
{ "sub": "1234567890", "name": "Ada Lovelace", "exp": 1735689600 }The signature is the third part. It is computed over the encoded header and payload using a key, and it's what makes the whole token trustworthy. We'll come back to exactly how it's made in a moment.
Why "encoded, not encrypted" matters so much
This is the single most misunderstood thing about JWTs. Base64URL is the same idea as Base64 — see the Base64 encoder/decoder — with a couple of characters swapped so the result is safe to put in a URL. It scrambles nothing. If you drop the payload section into the JWT decoder, you'll see the claims in plain JSON instantly, no key required. So a JWT protects you against forgery, not against reading. Treat everything in the payload as public information. If you genuinely need to hide the contents, that's a different mechanism (JWE, encrypted tokens) — a plain signed JWT is not it.
How signing and verification work
The signature is created by feeding the string base64url(header) + "." + base64url(payload) into a signing algorithm together with a key. There are two broad families:
- HMAC (HS256, HS384, HS512) uses a single shared secret. The same secret both signs the token and verifies it. It's based on HMAC — a keyed hash — and is fast and simple, but every party that can verify a token can also create one. Use it when signing and verifying happen inside the same trust boundary.
- RSA / ECDSA (RS256, ES256) use asymmetric keys. A private key signs the token and a separate public key verifies it. You can publish the public key freely so any number of services can check tokens, while only the holder of the private key can issue them. This is the right choice when verifiers should never be able to mint tokens.
Verification reverses the process: the server takes the header and payload it received, recomputes the signature with its key, and compares. If even one byte of the payload was changed in transit, the recomputed signature won't match and the token is rejected. Because an attacker doesn't know the secret (or private key), they can't produce a valid signature for a modified payload — that's the whole guarantee.
Common registered claims
The spec reserves a handful of short claim names. You don't have to use all of them, but you'll see these constantly:
| Claim | Name | Meaning |
|---|---|---|
iss | Issuer | Who created and signed the token (e.g. your auth server). |
sub | Subject | Who the token is about — usually the user ID. |
aud | Audience | Who the token is intended for; a recipient should reject tokens not meant for it. |
exp | Expiration | Unix time after which the token must not be accepted. |
nbf | Not Before | Unix time before which the token is not yet valid. |
iat | Issued At | Unix time the token was created; useful for age checks. |
jti | JWT ID | A unique identifier for the token, handy for revocation or replay prevention. |
Best practices
JWTs are safe when used carefully and dangerous when used carelessly. The essentials:
- Keep expiry short. A token can't be un-issued, so a stolen one is valid until it expires. Use a short
exp(minutes to an hour) for access tokens and pair it with a longer-lived refresh token that can be revoked. - Validate everything server-side. Always verify the signature and check
exp,nbf, and thataudmatches your service. A signature that's valid but expired or meant for someone else must still be rejected. - Pin the algorithm — never accept
alg: none. Some libraries historically honored an unsignednonealgorithm or let an attacker downgrade RS256 to HS256 using the public key as the HMAC secret. Configure your verifier to accept only the specific algorithm you expect. - Always use HTTPS. A bearer token is like cash — whoever holds it can use it. Transport it only over TLS so it can't be sniffed.
- Store it deliberately.
localStorageis convenient but readable by any script on the page, exposing it to XSS. AnhttpOnlycookie can't be read by JavaScript but is sent automatically, so it needsSameSiteand CSRF protection. For browser apps, httpOnly + SameSite cookies are usually the safer default.
The takeaway
A JWT is just three readable, signed pieces: a header that says how it was signed, a payload of claims, and a signature that proves nobody changed it. The signature buys you trust in the data, not secrecy of the data. Get the expiry, validation, and algorithm pinning right, keep secrets out of the payload, and JWTs give you clean, scalable, stateless authentication.
Frequently asked questions
- Is the data inside a JWT encrypted?
- No. The header and payload are only Base64URL-encoded, which is reversible by anyone — it is not encryption. Anybody who holds the token can decode and read the claims. The signature only proves the token has not been tampered with; it does not hide the contents. Never put passwords, secret keys, or sensitive personal data in a JWT payload.
- What stops someone from editing a JWT payload?
- The signature. It is computed over the encoded header and payload using a secret or private key. If an attacker changes any claim, the recomputed signature will not match, and the server rejects the token during verification. Because the attacker does not know the signing key, they cannot forge a valid signature for their edited payload.
- What is the difference between HS256 and RS256?
- HS256 uses HMAC with a single shared secret: the same key both signs and verifies, so every party that can verify can also mint tokens. RS256 uses RSA asymmetric keys: a private key signs and a public key verifies, so you can distribute the public key widely without letting anyone forge tokens. Use HS256 inside one trust boundary and RS256 (or ES256) when verifiers should not be able to sign.
- Should I store a JWT in localStorage or a cookie?
- It is a trade-off. localStorage is easy to use but readable by any JavaScript on the page, so it is exposed to cross-site scripting (XSS). An httpOnly cookie cannot be read by JavaScript, which blunts XSS, but it is sent automatically and must be protected against cross-site request forgery (CSRF) with the SameSite attribute and anti-CSRF tokens. For browser apps, httpOnly cookies with SameSite are usually the safer default.
This guide is general educational information about how JWTs work, not security advice for a specific system. Review your authentication design with a qualified professional before relying on it in production.