Overview
JSON Web Tokens (JWTs) are the standard way to authenticate users in stateless APIs. This tutorial explains the three-part JWT structure, walks through a login-and-protected-route flow, and covers the security pitfalls that cause real-world vulnerabilities.
What Is a JWT?
A JWT is a compact, URL-safe string with three parts separated by dots:
header.payload.signature
| Part | Content | Example |
|---|---|---|
| Header | Token type and signing algorithm | {"alg":"HS256","typ":"JWT"} |
| Payload | Claims such as user ID and expiry | {"sub":"123","exp":1735689600} |
| Signature | HMAC of header + payload using a secret | HMACSHA256(base64(header) + "." + base64(payload), secret) |
The payload is Base64URL-encoded, not encrypted. Anyone can read it. Never put passwords or secrets in a JWT payload.
Typical JWT Flow
- User sends credentials to
POST /api/auth/login. - Server verifies credentials and returns a signed JWT.
- Client stores the token (memory, secure cookie, or local storage).
- Client sends the token in the
Authorization: Bearer <token>header on subsequent requests. - Server verifies the signature and expiry on each request.
Node.js Implementation Example
const jwt = require('jsonwebtoken');
const SECRET = process.env.JWT_SECRET;
// Sign a token on login
function issueToken(userId) {
return jwt.sign(
{ sub: userId },
SECRET,
{ expiresIn: '1h' }
);
}
// Verify middleware
function authenticate(req, res, next) {
const header = req.headers.authorization || '';
const token = header.startsWith('Bearer ') ? header.slice(7) : null;
if (!token) {
return res.status(401).json({ error: 'Missing token' });
}
try {
const payload = jwt.verify(token, SECRET);
req.userId = payload.sub;
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
Install the jsonwebtoken package from the jsonwebtoken npm page.
Common Claims
| Claim | Meaning |
|---|---|
sub |
Subject, typically the user ID |
exp |
Expiration time as a Unix timestamp |
iat |
Issued at time |
iss |
Issuer |
aud |
Audience |
Security Pitfalls
| Pitfall | Risk | Mitigation |
|---|---|---|
| Storing JWT in localStorage | XSS can steal the token | Use HttpOnly, Secure cookies |
| Not verifying the algorithm | Algorithm confusion attack | Explicitly specify allowed algorithms |
| Long expiry without refresh | Stolen token valid for weeks | Use short-lived access tokens plus refresh tokens |
| Putting sensitive data in payload | Payload is readable | Only store non-sensitive identifiers |
Refresh Token Pattern
Issue two tokens: a short-lived access token (15 minutes) and a long-lived refresh token (7 days). When the access token expires, the client sends the refresh token to POST /api/auth/refresh to get a new access token. Store refresh tokens in the database so they can be revoked.
