JWT Essentials
Quick reference guide for JSON Web Tokens (JWT) - secure token-based authentication.
What is JWT?
JWT (JSON Web Token) is an open standard (RFC 7519) for securely transmitting information between parties as a JSON object:
JWT Structure
Three Parts
A JWT consists of three Base64URL-encoded parts separated by dots:
header.payload.signature
Example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
1. Header
Contains metadata about the token:
{
"alg": "HS256",
"typ": "JWT"
}
Common algorithms:
HS256 - HMAC with SHA-256 (symmetric)RS256 - RSA with SHA-256 (asymmetric)ES256 - ECDSA with SHA-256 (asymmetric)2. Payload
Contains claims (statements about the user):
{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"iat": 1516239022,
"exp": 1516242622
}
Registered Claims:
iss - Issuersub - Subject (user ID)aud - Audienceexp - Expiration timenbf - Not beforeiat - Issued atjti - JWT IDPublic Claims:
name, email, role)Private Claims:
3. Signature
Verifies the token hasn't been tampered with:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
How JWT Works
Authentication Flow
┌──────────┐ ┌──────────┐
│ Client │ │ Server │
└────┬─────┘ └────┬─────┘
│ │
│ 1. Login (credentials) │
│────────────────────────────────────>│
│ │
│ 2. Verify & Generate JWT
│ │
│ 3. Return JWT │
│<────────────────────────────────────│
│ │
│ 4. Request with JWT in header │
│────────────────────────────────────>│
│ │
│ 5. Verify JWT & Process
│ │
│ 6. Return protected resource │
│<────────────────────────────────────│
Creating JWTs
Node.js (jsonwebtoken)
const jwt = require('jsonwebtoken');
// ========== Create Token ==========
const payload = {
sub: '1234567890',
name: 'John Doe',
email: 'john@example.com',
role: 'admin'
};
const secret = 'your-secret-key';
const options = {
expiresIn: '1h',
issuer: 'your-app',
audience: 'your-api'
};
const token = jwt.sign(payload, secret, options);
console.log(token);
// ========== With RSA (Asymmetric) ==========
const privateKey = fs.readFileSync('private.key');
const token = jwt.sign(payload, privateKey, {
algorithm: 'RS256',
expiresIn: '1h'
});
Python (PyJWT)
import jwt
from datetime import datetime, timedelta
# ========== Create Token ==========
payload = {
'sub': '1234567890',
'name': 'John Doe',
'email': 'john@example.com',
'exp': datetime.utcnow() + timedelta(hours=1),
'iat': datetime.utcnow()
}
secret = 'your-secret-key'
token = jwt.encode(payload, secret, algorithm='HS256')
print(token)
# ========== With RSA ==========
with open('private.key', 'rb') as f:
private_key = f.read()
token = jwt.encode(payload, private_key, algorithm='RS256')
Java (jjwt)
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
// ========== Create Token ==========
String secret = "your-secret-key";
String token = Jwts.builder()
.setSubject("1234567890")
.claim("name", "John Doe")
.claim("email", "john@example.com")
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 3600000))
.signWith(SignatureAlgorithm.HS256, secret)
.compact();
System.out.println(token);
Go (jwt-go)
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5"
)
// ========== Create Token ==========
func createToken() string {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "1234567890",
"name": "John Doe",
"email": "john@example.com",
"exp": time.Now().Add(time.Hour * 1).Unix(),
"iat": time.Now().Unix(),
})
secret := []byte("your-secret-key")
tokenString, err := token.SignedString(secret)
if err != nil {
panic(err)
}
return tokenString
}
Verifying JWTs
Node.js
const jwt = require('jsonwebtoken');
// ========== Verify Token ==========
try {
const decoded = jwt.verify(token, 'your-secret-key');
console.log('Decoded:', decoded);
// Access claims
console.log('User ID:', decoded.sub);
console.log('Name:', decoded.name);
} catch (err) {
if (err.name === 'TokenExpiredError') {
console.error('Token expired');
} else if (err.name === 'JsonWebTokenError') {
console.error('Invalid token');
} else {
console.error('Verification failed:', err);
}
}
// ========== With Options ==========
const options = {
issuer: 'your-app',
audience: 'your-api',
algorithms: ['HS256']
};
const decoded = jwt.verify(token, secret, options);
// ========== Verify RSA ==========
const publicKey = fs.readFileSync('public.key');
const decoded = jwt.verify(token, publicKey, {
algorithms: ['RS256']
});
Python
import jwt
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
# ========== Verify Token ==========
try:
decoded = jwt.decode(
token,
'your-secret-key',
algorithms=['HS256'],
issuer='your-app',
audience='your-api'
)
print('Decoded:', decoded)
except ExpiredSignatureError:
print('Token expired')
except InvalidTokenError:
print('Invalid token')
# ========== Verify RSA ==========
with open('public.key', 'rb') as f:
public_key = f.read()
decoded = jwt.decode(token, public_key, algorithms=['RS256'])
Java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
// ========== Verify Token ==========
try {
Claims claims = Jwts.parser()
.setSigningKey("your-secret-key")
.parseClaimsJws(token)
.getBody();
System.out.println("User ID: " + claims.getSubject());
System.out.println("Name: " + claims.get("name"));
} catch (ExpiredJwtException e) {
System.err.println("Token expired");
} catch (JwtException e) {
System.err.println("Invalid token");
}
Express.js Middleware
Authentication Middleware
const jwt = require('jsonwebtoken');
// ========== Auth Middleware ==========
function authenticateToken(req, res, next) {
// Get token from header
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'Token expired' });
}
return res.status(403).json({ error: 'Invalid token' });
}
}
// ========== Protected Route ==========
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({
message: 'Protected data',
user: req.user
});
});
// ========== Login Route ==========
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
// Verify credentials (simplified)
const user = await User.findOne({ email });
if (!user || !await user.comparePassword(password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '1h' }
);
res.json({ token });
});
Role-Based Authorization
// ========== Role Middleware ==========
function requireRole(...roles) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
// ========== Protected Admin Route ==========
app.get('/api/admin',
authenticateToken,
requireRole('admin'),
(req, res) => {
res.json({ message: 'Admin data' });
}
);
// ========== Protected Multi-Role Route ==========
app.get('/api/dashboard',
authenticateToken,
requireRole('admin', 'manager'),
(req, res) => {
res.json({ message: 'Dashboard data' });
}
);
Refresh Tokens
Implementation
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
// ========== Generate Token Pair ==========
function generateTokens(user) {
// Access token (short-lived)
const accessToken = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role
},
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
// Refresh token (long-lived)
const refreshToken = crypto.randomBytes(40).toString('hex');
// Store refresh token in database with expiry
// (simplified - use proper database)
refreshTokens.set(refreshToken, {
userId: user.id,
expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days
});
return { accessToken, refreshToken };
}
// ========== Login Endpoint ==========
app.post('/api/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !await user.comparePassword(password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const tokens = generateTokens(user);
res.json({
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken
});
});
// ========== Refresh Endpoint ==========
app.post('/api/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Refresh token required' });
}
// Check if refresh token exists and is valid
const storedToken = refreshTokens.get(refreshToken);
if (!storedToken || storedToken.expiresAt < Date.now()) {
return res.status(403).json({ error: 'Invalid refresh token' });
}
// Get user
const user = await User.findById(storedToken.userId);
if (!user) {
return res.status(403).json({ error: 'User not found' });
}
// Generate new token pair
const tokens = generateTokens(user);
// Invalidate old refresh token
refreshTokens.delete(refreshToken);
res.json({
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken
});
});
// ========== Logout Endpoint ==========
app.post('/api/logout', (req, res) => {
const { refreshToken } = req.body;
if (refreshToken) {
refreshTokens.delete(refreshToken);
}
res.json({ message: 'Logged out successfully' });
});
Client-Side Token Management
// ========== Token Manager ==========
class TokenManager {
constructor() {
this.accessToken = localStorage.getItem('accessToken');
this.refreshToken = localStorage.getItem('refreshToken');
}
// Save tokens
setTokens(accessToken, refreshToken) {
this.accessToken = accessToken;
this.refreshToken = refreshToken;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
}
// Clear tokens
clearTokens() {
this.accessToken = null;
this.refreshToken = null;
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
}
// Get access token
getAccessToken() {
return this.accessToken;
}
// Refresh access token
async refreshAccessToken() {
try {
const response = await fetch('/api/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refreshToken: this.refreshToken })
});
if (!response.ok) {
throw new Error('Failed to refresh token');
}
const data = await response.json();
this.setTokens(data.accessToken, data.refreshToken);
return data.accessToken;
} catch (error) {
this.clearTokens();
throw error;
}
}
}
// ========== API Client with Auto-Refresh ==========
const tokenManager = new TokenManager();
async function apiCall(url, options = {}) {
// Add access token to request
const headers = {
...options.headers,
'Authorization': `Bearer ${tokenManager.getAccessToken()}`
};
let response = await fetch(url, { ...options, headers });
// If token expired, refresh and retry
if (response.status === 401) {
try {
const newToken = await tokenManager.refreshAccessToken();
headers['Authorization'] = `Bearer ${newToken}`;
response = await fetch(url, { ...options, headers });
} catch (error) {
// Redirect to login
window.location.href = '/login';
throw error;
}
}
return response;
}
// Usage
apiCall('/api/protected')
.then(res => res.json())
.then(data => console.log(data))
.catch(err => console.error(err));
Security Best Practices
Token Storage
// ========== ✅ GOOD: Secure HTTP-only Cookie ==========
app.post('/api/login', async (req, res) => {
const tokens = generateTokens(user);
// Store refresh token in HTTP-only cookie
res.cookie('refreshToken', tokens.refreshToken, {
httpOnly: true,
secure: true, // HTTPS only
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
res.json({ accessToken: tokens.accessToken });
});
// ========== ❌ BAD: localStorage for sensitive tokens ==========
// localStorage is vulnerable to XSS attacks
localStorage.setItem('refreshToken', refreshToken); // Don't do this!
Secret Key Management
// ========== ✅ GOOD: Environment Variables ==========
const secret = process.env.JWT_SECRET;
// ========== ✅ GOOD: Strong Secret ==========
// Generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
// Example: 4f8a3c2e1d9b7a6f5e4d3c2b1a9e8d7c6b5a4f3e2d1c9b8a7f6e5d4c3b2a1f0
// ========== ❌ BAD: Hardcoded Secret ==========
const secret = 'mysecret'; // Too simple!
const secret = 'your-secret-key'; // Never commit to git!
Token Validation
// ========== Complete Validation ==========
function validateToken(token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // Specify allowed algorithms
issuer: 'your-app',
audience: 'your-api'
});
// Check expiration
if (decoded.exp < Date.now() / 1000) {
throw new Error('Token expired');
}
// Check not before
if (decoded.nbf && decoded.nbf > Date.now() / 1000) {
throw new Error('Token not yet valid');
}
// Additional checks
if (!decoded.sub) {
throw new Error('Missing subject');
}
return decoded;
} catch (error) {
throw new Error(`Token validation failed: ${error.message}`);
}
}
Algorithm Confusion Attack
// ========== ✅ GOOD: Specify Algorithm ==========
jwt.verify(token, secret, { algorithms: ['HS256'] });
// ========== ❌ BAD: No Algorithm Specified ==========
// Attacker can change algorithm in header to "none"
jwt.verify(token, secret);
Payload Security
// ========== ✅ GOOD: Minimal Payload ==========
const payload = {
sub: user.id,
role: user.role,
exp: Math.floor(Date.now() / 1000) + 3600
};
// ========== ❌ BAD: Sensitive Data ==========
const payload = {
sub: user.id,
password: user.password, // Never include passwords!
ssn: user.ssn, // Never include sensitive PII!
creditCard: user.creditCard // Never include payment info!
};
Common Use Cases
API Authentication
// ========== Header Format ==========
// Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// ========== Client Request ==========
fetch('/api/users', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
})
.then(res => res.json())
.then(data => console.log(data));
Single Sign-On (SSO)
// ========== Identity Provider ==========
app.post('/sso/login', async (req, res) => {
const user = await authenticateUser(req.body);
const token = jwt.sign(
{
sub: user.id,
email: user.email,
name: user.name
},
process.env.JWT_SECRET,
{
expiresIn: '1h',
issuer: 'identity-provider',
audience: ['app1', 'app2', 'app3']
}
);
res.redirect(`https://app1.example.com/callback?token=${token}`);
});
// ========== Service Provider ==========
app.get('/callback', (req, res) => {
const token = req.query.token;
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
issuer: 'identity-provider',
audience: 'app1'
});
// Create local session
req.session.user = decoded;
res.redirect('/dashboard');
} catch (error) {
res.redirect('/login?error=invalid_token');
}
});
Stateless Sessions
// ========== Create Session ==========
app.post('/api/login', async (req, res) => {
const user = await authenticateUser(req.body);
const sessionToken = jwt.sign(
{
sub: user.id,
email: user.email,
role: user.role,
sessionId: crypto.randomUUID()
},
process.env.JWT_SECRET,
{ expiresIn: '24h' }
);
res.cookie('session', sessionToken, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});
res.json({ message: 'Logged in' });
});
// ========== Session Middleware ==========
function sessionMiddleware(req, res, next) {
const token = req.cookies.session;
if (!token) {
return res.status(401).json({ error: 'Not authenticated' });
}
try {
req.session = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (error) {
res.clearCookie('session');
return res.status(401).json({ error: 'Invalid session' });
}
}
JWT vs Sessions
JWT Advantages
Session Advantages
When to Use JWT
When to Use Sessions
Common Vulnerabilities
1. Weak Secret Key
// ❌ Vulnerable
const secret = 'secret';
// ✅ Secure
const secret = process.env.JWT_SECRET; // 32+ character random string
2. No Expiration
// ❌ Vulnerable
const token = jwt.sign(payload, secret); // No expiration!
// ✅ Secure
const token = jwt.sign(payload, secret, { expiresIn: '1h' });
3. Sensitive Data in Payload
// ❌ Vulnerable
const payload = { userId: 1, password: 'secret123' };
// ✅ Secure
const payload = { sub: 1, role: 'user' };
4. Not Validating Algorithms
// ❌ Vulnerable
jwt.verify(token, secret);
// ✅ Secure
jwt.verify(token, secret, { algorithms: ['HS256'] });
5. Storing in localStorage
// ❌ Vulnerable to XSS
localStorage.setItem('token', token);
// ✅ More secure
// Use HTTP-only cookies or memory storage
Debugging JWTs
Decode Without Verification
// ========== Node.js ==========
const jwt = require('jsonwebtoken');
const decoded = jwt.decode(token, { complete: true });
console.log('Header:', decoded.header);
console.log('Payload:', decoded.payload);
console.log('Signature:', decoded.signature);
// ========== Manual Decode ==========
const [header, payload, signature] = token.split('.');
const decodedHeader = JSON.parse(
Buffer.from(header, 'base64url').toString()
);
const decodedPayload = JSON.parse(
Buffer.from(payload, 'base64url').toString()
);
console.log('Header:', decodedHeader);
console.log('Payload:', decodedPayload);
Online Tools
Logging
// ========== Debug Middleware ==========
function logJWT(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (token) {
const decoded = jwt.decode(token, { complete: true });
console.log('JWT Debug:', {
header: decoded.header,
payload: decoded.payload,
issuedAt: new Date(decoded.payload.iat * 1000),
expiresAt: new Date(decoded.payload.exp * 1000),
isExpired: decoded.payload.exp < Date.now() / 1000
});
}
next();
}
app.use(logJWT);