JWT (JSON Web Tokens)
Updated June 3, 2026Take any JWT — those long strings that look like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIiwiaWF0IjoxNTE2MjM5MDIyfQ
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5cDecode the middle part from base64 and you'll find plain JSON. Not encrypted, not hashed. Just encoded.
This surprises a lot of people. "So anyone can read my token?" Yes. But no one can forge one without the secret key. Understanding this distinction is the key to understanding JWTs.
JWT three-part structure — header.payload.signature with base64url encoding
Structure of a JWT
A JWT is three base64url-encoded sections joined by dots:
header.payload.signatureHeader
The header is a JSON object specifying the token type and the signing algorithm:
{
"alg": "HS256",
"typ": "JWT"
}alg tells the verifier which algorithm was used to create the signature. Common options:
HS256: HMAC-SHA256 (symmetric, same secret for signing and verifying)RS256: RSA-SHA256 (asymmetric, private key signs, public key verifies)ES256: ECDSA with P-256 (asymmetric, smaller keys than RSA)
Payload
The payload contains the actual data — these are called claims:
{
"sub": "user_123",
"name": "Alice Johnson",
"email": "alice@example.com",
"roles": ["admin", "editor"],
"iat": 1705315200,
"exp": 1705318800
}Standard claims (defined in the JWT spec):
sub(subject) — who the token is about, typically a user IDiss(issuer) — who created the tokenaud(audience) — who the token is intended forexp(expiration) — Unix timestamp after which the token is invalidiat(issued at) — when the token was creatednbf(not before) — the token isn't valid before this timestamp
Custom claims: Anything else you add. Roles, permissions, tenant ID, plan tier — you can put any JSON-serializable data here. Just be mindful that this data is included in every request.
Signature
The signature is what makes the token trustworthy. It's computed by taking the encoded header + encoded payload and signing them with a secret key:
HMACSHA256(
base64url(header) + "." + base64url(payload),
secret_key
)When the server receives a token, it recomputes the signature using the same key and compares it to the signature in the token. If they match, the payload is authentic and unmodified. If someone tampers with the payload (changes "roles": ["user"] to "roles": ["admin"]), the signature won't match and the token is rejected.
Which part of a JWT contains the actual user data and claims?
Cryptographic verification — server recomputes HMAC, compares to token signature
How JWT Verification Works Without a Database
This is the elegant part. When your API receives a request with a JWT:
- Split the token into header, payload, and signature
- Recompute the expected signature from the header + payload using your secret key
- Compare to the provided signature — if they match, the token is valid
- Check the
expclaim — if expired, reject - Read the
suband any other claims — you now know who the user is and what they're allowed to do
No database lookup required. The server validates the token purely with cryptographic operations. This is what makes JWTs scale horizontally — any server with the secret key can validate any token, independently.
Compare this to session-based auth, where every request requires a Redis lookup. With JWTs, your backend services can validate tokens in microseconds with no external dependency.
JWT payload data is encrypted, so it cannot be read without the secret key.
What makes JWT verification possible without a database lookup?
Symmetric vs Asymmetric Signing
Symmetric (HS256): A single secret key is used for both signing and verifying. Fast and simple. Works well when the same service issues and verifies tokens. Problem: every service that needs to verify tokens needs the secret — which means it can also issue tokens. Sharing a secret broadly increases the attack surface.
Asymmetric (RS256, ES256): A private key signs tokens; a public key verifies them. Only the auth service holds the private key. Every other service gets the public key, which lets them verify but not create tokens. This is the right approach when:
- Multiple services need to verify tokens
- You're issuing tokens for third-party consumption
- You want to make your public key available via a JWKS (JSON Web Key Set) endpoint so clients can discover it automatically
Most identity providers (Auth0, Cognito, Google) publish their public keys at a well-known URL (e.g., https://your-domain.auth0.com/.well-known/jwks.json), which is how your API knows how to verify tokens they issue.
With asymmetric signing (RS256), which key should be distributed to services that only need to verify tokens?
JWT Pitfalls
Expiry — Set It Short
JWTs are irrevocable until they expire. If a token is stolen or a user's account is suspended, you can't invalidate it. It stays valid until exp.
The solution is short expiry times. Access tokens should expire in 15 minutes to 1 hour. Pair them with refresh tokens: longer-lived tokens (7-30 days) stored securely, used only to get a new access token when the current one expires.
If you need true instant revocation, maintain a token blocklist in Redis: a set of token IDs (jti claim) that have been revoked. Check every incoming token against the blocklist. This re-introduces state but preserves the scalability benefits for the 99.9% of valid tokens.
Why should JWT access tokens have short expiry times (15–60 minutes)?
The "none" Algorithm Vulnerability
Early JWT libraries had a catastrophic bug: if you sent a token with "alg": "none" in the header, some libraries accepted it as valid without verifying the signature. This let attackers craft arbitrary tokens.
Modern libraries have fixed this, but always configure your library to explicitly whitelist allowed algorithms. Never accept "alg": "none".
Key Rotation
If your signing key is compromised, all tokens signed with it must be considered invalid. You need a process for rotating keys without logging everyone out simultaneously:
- Assign a key ID (
kid) to each key - Include the
kidin the JWT header - Support multiple active keys simultaneously during rotation
- Retire old keys after all tokens signed with them have expired
What to Put in localStorage vs HttpOnly Cookies
This is a genuine security debate:
localStorage:
- Easy to implement, accessible by JavaScript
- Vulnerable to XSS — any JavaScript on your page can read it, including injected malicious scripts
HttpOnly Cookie:
- Not accessible by JavaScript — XSS attacks can't steal it
- Automatically sent with requests
- Vulnerable to CSRF — you need CSRF tokens or SameSite cookie attribute as mitigation
- Cross-origin complexity
The modern consensus: Store tokens in HttpOnly, Secure, SameSite=Strict cookies. Use CSRF protection. Avoid localStorage for anything security-sensitive.
A third option gaining traction: store tokens in memory (a JavaScript variable). Tokens are lost on page refresh, but you can silently re-issue them via a refresh token in an HttpOnly cookie. This eliminates both XSS and CSRF risks at the cost of some implementation complexity.
Storing JWTs in localStorage is safer than HttpOnly cookies because JavaScript can manage them directly.
Summary
JWTs are a powerful tool for stateless authentication — they carry identity and claims without requiring server-side session storage. The structure is always header.payload.signature: the payload is readable by anyone, but the signature is cryptographically verified, making forgery impossible without the secret key. Verification requires no database lookup — just cryptography — which is what makes JWTs scale horizontally. Use asymmetric signing (RS256/ES256) when multiple services need to verify tokens, keep access token expiry short (15–60 min) and pair with refresh tokens for long-lived sessions, and store tokens in HttpOnly cookies rather than localStorage. The classic pitfalls — algorithm confusion, missing expiry checks, and unmanaged key rotation — are avoidable once you understand them.
JWTs are not magic — they're base64-encoded JSON with a signature. Understanding exactly what that means makes you a much safer implementer of authentication systems.
How helpful was this content?
Comments
Sign in to join the discussion
Saved on this device only
Sign in to sync progress across devices