JSON Web Token (JWT)
A JWT is a tamper-proof ID badge your server issues after you log in. Instead of making you show your passport on every request, the server stamps a badge with your name and permissions, seals it with a secret wax stamp, and hands it to you. On each future request, you show the badge; the server verifies the wax stamp is genuine and reads your details without ever looking you up in a database again. If anyone modifies the badge, the stamp breaks and the server rejects it.
A JSON Web Token (JWT), defined in RFC 7519, is a URL-safe compact representation of claims transferred between parties. A JWT consists of three Base64URL-encoded segments separated by dots:
<base64url-header>.<base64url-payload>.<base64url-signature>
# Concrete example (structure only -- not a valid token):
# header: {"alg":"HS256","typ":"JWT"}
# payload: {"sub":"user_123","exp":1713200000}
# signature: HMAC-SHA256(header + "." + payload, secret)Header (algorithm and token type):
{ "alg": "HS256", "typ": "JWT" }Payload (claims, not encrypted, only encoded):
| Claim | Name | Description |
|---|---|---|
iss | Issuer | Who created the token (e.g., https://auth.example.com) |
sub | Subject | Who the token is about (user ID) |
aud | Audience | Who should accept the token |
exp | Expiration | Unix timestamp after which the token is invalid |
iat | Issued At | Unix timestamp when the token was created |
nbf | Not Before | Token is invalid before this timestamp |
jti | JWT ID | Unique identifier, used for revocation |
Signature (prevents tampering):
- HS256 (HMAC-SHA256): symmetric; server signs and verifies with the same secret key
- RS256 (RSA-SHA256): asymmetric; private key signs, public key verifies (safe for distributed systems)
- ES256 (ECDSA): asymmetric, smaller keys than RSA
Security requirements:
- Always validate
expbefore trusting the payload - Use
RS256orES256in distributed systems so microservices can verify without sharing secrets - Store JWTs in
HttpOnlycookies (notlocalStorage) to prevent XSS theft - Implement token rotation: short-lived access tokens (15 min) + long-lived refresh tokens
- Maintain a token revocation list (Redis blocklist) using the
jticlaim for logout
JWT creation, inspection, and validation
# Decode a JWT without verification (never trust unverified)
# Replace $TOKEN with a real token from your auth flow
$ echo "$TOKEN" | cut -d. -f2 | base64 -d 2>/dev/null | jq .
{
"sub": "user_123",
"role": "admin",
"exp": 1713200000
}
# Verify signature and decode with jwt-cli
$ jwt decode --secret "$JWT_SECRET" "$TOKEN"// Node.js: sign and verify JWTs
import jwt from "jsonwebtoken";
const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET!;
export function signAccessToken(userId: string, role: string): string {
return jwt.sign(
{ sub: userId, role },
ACCESS_SECRET,
{ algorithm: "HS256", expiresIn: "15m" }
);
}
export function verifyAccessToken(token: string): jwt.JwtPayload {
// Throws on invalid signature, expired token, or malformed input
return jwt.verify(token, ACCESS_SECRET) as jwt.JwtPayload;
}
// Middleware: validate JWT on protected routes
export function requireAuth(req: Request, res: Response, next: NextFunction): void {
const token = req.cookies["access_token"];
if (!token) { res.status(401).json({ error: "Unauthorized" }); return; }
try {
res.locals.user = verifyAccessToken(token);
next();
} catch {
res.status(401).json({ error: "Invalid or expired token" });
}
} JWTs are everywhere in modern web auth. Auth0, Clerk, Supabase Auth, and Cognito all issue JWTs. The most common vulnerability is storing them in localStorage, which exposes them to any XSS script on the page. HttpOnly cookies are immune to JavaScript access. The second most common mistake is not validating exp, trusting a token indefinitely after it was issued. The “algorithm confusion attack” (setting alg: none in a forged header) was a real class of vulnerability in early JWT libraries; production libraries now reject none by default. For stateless API auth, JWTs eliminate the need for a session database lookup on every request, trading revocability for performance. The standard pattern is: 15-minute access JWT in a HttpOnly cookie, 7-day refresh token in a separate HttpOnly cookie, server-side revocation list for logout.