JWT Tokens Explained: A Practical Authentication Guide (2026)
JSON Web Tokens power authentication for thousands of APIs. Learn exactly how JWTs work, the difference between HS256 and RS256, common security mistakes, and how to debug them. Includes real decoded examples.
If you've ever logged into a website or used an API, you've almost certainly used a JWT (JSON Web Token) without realizing it. JWTs are the de facto standard for stateless authentication in modern web applications. They're used by Auth0, Firebase, Supabase, GitHub, Google, and thousands of other services.
This guide explains how JWTs work byte-by-byte, the algorithms behind them, the common security pitfalls that have led to real breaches, and how to actually debug them when something goes wrong.
What Is a JWT, Exactly?
A JWT (JSON Web Token, defined in RFC 7519) is a compact, URL-safe way to transmit claims between two parties. Visually, it's a long string that looks like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Notice the two . separators — they divide the token into three parts, each Base64URL-encoded (note: URL-safe Base64, not standard Base64):
- Header — algorithm and token type
- Payload — the claims (data being transmitted)
- Signature — cryptographic proof the token wasn't tampered with
The Three Parts in Detail
Header (part 1):
{
"alg": "HS256",
"typ": "JWT"
}
The header tells the receiver which algorithm to use for verification. alg is mandatory; typ is optional but standard.
Payload (part 2):
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
The payload contains the claims — statements about the user or session. There are three types of claims:
- Registered claims — standardized names like
iss(issuer),sub(subject),aud(audience),exp(expiration),iat(issued at),jti(JWT ID). - Public claims — names registered in the IANA "JSON Web Token Claims" registry or namespaced to avoid collisions.
- Private claims — custom names agreed between producer and consumer. Use these carefully — they're not interoperable.
Signature (part 3):
The signature is the cryptographic glue that holds the system together. For HS256 (the most common algorithm), it's:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
Anyone with the secret can verify the signature was produced by someone else with the secret. Anyone without the secret can verify it — but they cannot create a new signature.
How Authentication Actually Works With JWTs
- User submits credentials (username + password) to the auth server.
- Auth server validates credentials and returns a JWT signed with a secret.
- Client stores the JWT (typically in memory or a cookie) and sends it on every subsequent request:
Authorization: Bearer <jwt>. - The server (or any service with the secret) verifies the signature and trusts the claims inside.
- If the JWT is expired (
expclaim), the user must re-authenticate.
The key advantage: the server doesn't need to look anything up. The token itself is proof. This makes JWTs perfect for stateless, distributed systems where multiple services need to trust the same token.
HS256 vs RS256 vs ES256 — Which Algorithm to Use
HS256 (HMAC + SHA-256):
- Symmetric: same secret signs and verifies
- Fast
- Best for single-service applications where one party both signs and verifies
- Secret must be kept absolutely private
RS256 (RSA + SHA-256):
- Asymmetric: private key signs, public key verifies
- Slower than HS256 but still fast
- Best for OAuth providers, distributed systems where many services verify but only one signs
- Public keys can be shared via JWKS endpoint
ES256 (ECDSA + SHA-256):
- Asymmetric, uses elliptic curves (P-256)
- Smaller signatures and keys than RS256
- Same use case as RS256 but more modern
Rule of thumb: Use HS256 for single-app auth. Use RS256/ES256 when you have an OAuth-style auth server that multiple independent services need to verify tokens from.
Common JWT Security Mistakes (With Real Examples)
1. alg: "none"
Some early JWT libraries accepted tokens with alg: "none" — meaning no signature required. An attacker could craft a token with arbitrary claims and no signature, and the server would accept it.
Fix: Always specify the expected algorithm explicitly when verifying. Never trust the alg header blindly.
2. Algorithm Confusion (RS256 → HS256)
The classic 2015 Auth0-discovered vulnerability: if a server uses RS256 (asymmetric) but accepts HS256 tokens signed with the public key, an attacker can fetch the public key and use it as the HS256 secret to forge any token they want.
Fix: Pin the algorithm in your verification code. Don't let the attacker's choice of alg influence yours.
3. Storing Sensitive Data in the Payload
The payload is Base64-encoded, not encrypted. Anyone who gets the token can read every claim. Putting passwords, API keys, or PII in the payload is the same as publishing it in plaintext.
Fix: Only put identifiers (user IDs) and minimal non-sensitive metadata in JWT claims.
4. Not Validating exp
If you skip expiration validation, leaked tokens are valid forever. Some libraries make exp validation optional or off by default.
Fix: Set short token lifetimes (15 min for access tokens, 7 days for refresh tokens with rotation).
5. Using "alg: HS256" With a Static Config Secret
If your secret is something like "secret" or shipped in version control, attackers can forge tokens instantly.
Fix: Generate cryptographically random secrets (256+ bits), store them in environment variables or a secrets manager, rotate periodically.
How to Debug a JWT
When something goes wrong, follow this checklist:
- Decode the token — check the header and payload. Use the LaiUse JWT Decoder to inspect a token instantly.
- Check the algorithm — does the
algin the token match what your server expects? - Check expiration — is the
expclaim in the past? - Check issuer/audience — does the
issandaudmatch expected values? - Check the signature — does it actually validate with your secret? Most decoding tools will verify the signature if you provide the secret.
- Check clock skew — if your server's clock is 5 minutes ahead of the auth server's, a fresh token may appear expired. Most libraries allow a small clock skew leeway (30-60 seconds).
HS256 Code Example (Python)
import jwt
import datetime
# Signing a token
payload = {
"sub": "user-12345",
"name": "Alice",
"iat": datetime.datetime.utcnow(),
"exp": datetime.datetime.utcnow() + datetime.timedelta(minutes=15)
}
token = jwt.encode(payload, "your-256-bit-secret", algorithm="HS256")
# Verifying a token (raises jwt.InvalidTokenError if invalid)
decoded = jwt.decode(token, "your-256-bit-secret", algorithms=["HS256"])
print(decoded["sub"]) # "user-12345"
HS256 Code Example (Node.js)
const jwt = require('jsonwebtoken');
const token = jwt.sign(
{ sub: 'user-12345', name: 'Alice' },
'your-256-bit-secret',
{ expiresIn: '15m', algorithm: 'HS256' }
);
const decoded = jwt.verify(token, 'your-256-bit-secret', {
algorithms: ['HS256'] // CRUCIAL: pin the algorithm
});
Try It Yourself
Got a JWT you're trying to debug? Paste it into our free JWT Decoder to see the header and payload decoded instantly. The tool runs entirely in your browser — no token data leaves your device.
Want to understand the encoding under the hood? Read our Base64 Encoding Explained guide — JWTs use the URL-safe Base64 variant.
Need to generate cryptographically random secrets for your signing key? Use our Password Generator with the "Cryptographic" option for true random output from your browser's CSPRNG.
About LaiUse
LaiUse is a free browser-based productivity platform with 178 tools across 12 categories. Every tool runs in your browser — your files never leave your device. Read more on our about page, or browse all free tools.