May 20, 2026
How JWT Authentication Actually Works (and How to Read One)
A JWT is three Base64URL segments — header, payload, signature — where only the signature is secret. Reading a token proves nothing; only verifying the signature does.

JWTs (JSON Web Tokens) are the most common way modern APIs handle authentication statelessly: instead of the server keeping a session table and sending back an opaque cookie ID, the server hands the client a signed token that contains the user's claims directly, and every subsequent request carries that token in an Authorization: Bearer header. The trick is that the token is signed, so the server can trust its contents without storing anything — but "signed" and "encrypted" are different things, and confusing them is where most JWT mistakes happen.
The three-part structure
A JWT is three Base64URL-encoded segments joined by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 <- header
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9l <- payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c <- signature
Each segment is independently Base64URL-decoded into JSON or raw bytes:
const [headerB64, payloadB64, signatureB64] = token.split(".");
// header -> {"alg":"HS256","typ":"JWT"}
// payload -> {"sub":"1234567890","name":"John Doe","iat":1516239022}
The header describes the algorithm used to sign the token (typically HS256 for HMAC-SHA256, or RS256 for an RSA signature). The payload carries the claims — things like the subject, issued-at, expiration, and whatever custom fields your app puts in there. The signature is the part that makes the token trustworthy: it is computed over the Base64URL of the header, a dot, and the Base64URL of the payload, using the algorithm in the header and a secret key only the server knows.
Try it: JWT Decoder
Decode a JSON Web Token's header and payload — no signature verification.
Reading a token is not the same as trusting it
Here is the part that catches people: the header and payload are not encrypted. They are just Base64URL-encoded, which means anyone who can read the token off the wire can decode and read them — including the browser DevTools network tab, a logging proxy, or anyone with access to the request. The JWT Decoder does exactly this, instantly, with no secret required.
This is by design. A JWT is a self-contained signed assertion, not a confidential envelope. The signature lets the server verify that the claims have not been tampered with; it does nothing to hide them. Two practical consequences fall out of that:
- Never put sensitive data in the payload. No passwords, no API keys, no PII you would not want logged. Treat the payload as a postcard, not a sealed letter.
- Always transport tokens over HTTPS. TLS is what keeps an eavesdropper from reading the payload in transit; the JWT signature cannot do that job.
Decoding a JWT and verifying a JWT are different operations. Decoding is the Base64URL step above — it reads the claims but proves nothing. Anyone can craft a token that decodes to a payload claiming admin privileges. Verification is the cryptographic step: recomputing the signature with the server's secret and comparing it to the one on the token. Only a verified token should ever be trusted to identify a user.
How the signature actually works
For an HS256 token, the server computes the signature as:
HMAC-SHA256(
secret,
base64url(header) + "." + base64url(payload)
)
HMAC-SHA256 is a keyed hash: the same input plus the same secret always produces the same output, but without the secret you cannot produce a valid signature for a chosen payload. So when the server receives a token, it re-runs that computation and compares the result to the signature segment. A single bit changed in the payload — flipping a user ID by one digit, say — produces a completely different signature, and the check fails.
The Hash Generator is useful for seeing how SHA-256 itself behaves on arbitrary input, though note that JWT signatures use HMAC (keyed), not plain SHA-256 — the difference matters because a plain hash has no secret and can be recomputed by anyone.
For RS256 and other asymmetric algorithms, the signing side uses a private key and the verifying side uses the corresponding public key. That lets services verify tokens without ever holding the signing key, which is why larger systems with many services tend to prefer RS256.
Try it: Base64 Encode / Decode
Encode text to Base64 or decode Base64 back to text — instantly, in your browser.
Standard claims worth knowing
A few standard claims show up in almost every JWT, and knowing them saves a lot of guesswork:
exp(expiration) — a Unix timestamp after which the token must be rejected. Always set a short one.iat(issued at) — when the token was created, also a Unix timestamp.sub(subject) — the user or principal the token represents.iss(issuer) — who issued the token, useful in multi-tenant setups.aud(audience) — who the token is intended for.
The exp claim is your primary defense against a stolen token being usable forever — tokens should expire in minutes to hours, not weeks, with refresh handled through a separate refresh token rather than by stretching the access token's lifetime. To make sense of the iat and exp values, which are plain Unix timestamps, the Timestamp Converter turns them into human-readable dates instantly.
Practical handling rules
Treat the token as a bearer credential: whoever holds it can act as the user until it expires, so store it carefully (an HttpOnly cookie is a safer default for browser apps than localStorage), set short expirations, and always verify both the signature and the exp claim on the server before trusting any field in the payload. Reading a token in DevTools or with a decoder is fine for debugging; relying on its contents without verifying the signature is the single most common JWT security mistake.