Documentation & Learning Resources
Back to Tool

Secure JWT Implementation Guide

Security Best Practices Reading time: 12 min

This guide provides practical advice for implementing JWT authentication securely in your applications. It covers best practices, common pitfalls, and code examples for various programming languages.

While this guide aims to be comprehensive, security is a complex and evolving field. Always stay updated with the latest security recommendations and adapt these practices to your specific security requirements and threat model.

JWT Authentication Flow

A typical JWT authentication flow consists of the following steps:

  1. User Login: User provides credentials (username/password)
  2. Token Generation: Server validates credentials and generates a JWT
  3. Token Storage: Client stores the JWT (localStorage, cookies, etc.)
  4. Authenticated Requests: Client includes the JWT in subsequent requests
  5. Token Verification: Server verifies the JWT for each protected request
  6. Response: Server processes the request if the JWT is valid

Recommended Flow with Refresh Tokens

For enhanced security, implement a refresh token system:

  1. User Login: User provides credentials
  2. Token Generation: Server generates both an access token (short-lived JWT) and a refresh token (longer-lived, stored securely)
  3. Token Usage: Client uses the access token for API requests
  4. Token Refresh: When the access token expires, client uses the refresh token to obtain a new access token without requiring re-authentication
  5. Token Revocation: Refresh tokens can be revoked to invalidate all derived access tokens
JWT Authentication Flow with Refresh Tokens

Secure JWT Configuration

Token Payload

Include only necessary claims in your JWT payload:

{
  "sub": "user123",        // Subject (user identifier)
  "iat": 1516239022,       // Issued at (timestamp)
  "exp": 1516242622,       // Expiration time (timestamp)
  "aud": "my-api",         // Audience (intended recipient)
  "iss": "my-auth-server", // Issuer (who created the token)
  "jti": "unique-id-123"   // JWT ID (unique identifier for this token)
}

Security Parameters

Token Expiration

  • Access tokens: 15-30 minutes
  • Refresh tokens: 1-7 days (stored securely)

Algorithm Selection

  • Prefer asymmetric algorithms (RS256, ES256) over symmetric ones (HS256)
  • Avoid using "none" algorithm (disable it in your library)

Key Management

  • For symmetric algorithms: Use a strong, randomly generated secret (at least 256 bits)
  • For asymmetric algorithms: Use proper key lengths (RSA: 2048+ bits, EC: P-256 or higher)
  • Rotate keys periodically
  • Use a secure key management service when possible

Token Storage (Client-side)

  • Prefer HttpOnly cookies with Secure and SameSite flags for web applications
  • If using localStorage/sessionStorage, be aware of XSS risks
  • Mobile apps: Use secure storage mechanisms provided by the platform

Implementation Examples

Node.js Implementation

const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const express = require('express');
const bcrypt = require('bcrypt');

const app = express();
app.use(express.json());

// Use a strong secret or asymmetric keys
const JWT_SECRET = process.env.JWT_SECRET || crypto.randomBytes(32).toString('hex');
const JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || crypto.randomBytes(32).toString('hex');

// Token blacklist (use Redis in production)
const tokenBlacklist = new Set();
const refreshTokens = new Map();

// Generate token pair
function generateTokens(user) {
    const payload = {
        sub: user.id,
        username: user.username,
        role: user.role,
        permissions: user.permissions
    };
    
    const accessToken = jwt.sign(payload, JWT_SECRET, {
        expiresIn: '15m',
        issuer: 'secure-app',
        audience: 'secure-app-users',
        jwtid: crypto.randomUUID()
    });
    
    const refreshToken = crypto.randomBytes(32).toString('hex');
    refreshTokens.set(refreshToken, {
        userId: user.id,
        expiresAt: Date.now() + 7 * 24 * 60 * 60 * 1000 // 7 days
    });
    
    return { accessToken, refreshToken };
}

// Middleware to validate access tokens
function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1];
    
    if (!token) {
        return res.status(401).json({ error: 'Access token required' });
    }
    
    try {
        const decoded = jwt.verify(token, JWT_SECRET, {
            issuer: 'secure-app',
            audience: 'secure-app-users'
        });
        
        // Check if token is blacklisted
        if (tokenBlacklist.has(decoded.jti)) {
            return res.status(401).json({ error: 'Token has been revoked' });
        }
        
        req.user = decoded;
        next();
    } catch (err) {
        return res.status(403).json({ error: 'Invalid or expired token' });
    }
}

// Login endpoint
app.post('/auth/login', async (req, res) => {
    const { username, password } = req.body;
    
    try {
        // Validate credentials (replace with your user validation)
        const user = await validateUser(username, password);
        if (!user) {
            return res.status(401).json({ error: 'Invalid credentials' });
        }
        
        const tokens = generateTokens(user);
        
        // Set refresh token as httpOnly cookie
        res.cookie('refreshToken', tokens.refreshToken, {
            httpOnly: true,
            secure: process.env.NODE_ENV === 'production',
            sameSite: 'strict',
            maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
        });
        
        res.json({
            accessToken: tokens.accessToken,
            user: {
                id: user.id,
                username: user.username,
                role: user.role
            }
        });
    } catch (err) {
        res.status(500).json({ error: 'Login failed' });
    }
});

// Refresh token endpoint
app.post('/auth/refresh', (req, res) => {
    const refreshToken = req.cookies.refreshToken;
    
    if (!refreshToken || !refreshTokens.has(refreshToken)) {
        return res.status(401).json({ error: 'Invalid refresh token' });
    }
    
    const tokenData = refreshTokens.get(refreshToken);
    
    if (Date.now() > tokenData.expiresAt) {
        refreshTokens.delete(refreshToken);
        return res.status(401).json({ error: 'Refresh token expired' });
    }
    
    // Generate new tokens
    const user = getUserById(tokenData.userId);
    const tokens = generateTokens(user);
    
    // Remove old refresh token
    refreshTokens.delete(refreshToken);
    
    // Set new refresh token
    res.cookie('refreshToken', tokens.refreshToken, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 7 * 24 * 60 * 60 * 1000
    });
    
    res.json({ accessToken: tokens.accessToken });
});

// Logout endpoint
app.post('/auth/logout', authenticateToken, (req, res) => {
    // Add token to blacklist
    tokenBlacklist.add(req.user.jti);
    
    // Remove refresh token
    const refreshToken = req.cookies.refreshToken;
    if (refreshToken) {
        refreshTokens.delete(refreshToken);
    }
    
    res.clearCookie('refreshToken');
    res.json({ message: 'Logged out successfully' });
});

// Protected route example
app.get('/protected', authenticateToken, (req, res) => {
    res.json({
        message: 'This is a protected route',
        user: req.user
    });
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

Python Implementation (Flask)

from flask import Flask, request, jsonify, make_response
import jwt
import bcrypt
from datetime import datetime, timedelta
import os
import secrets
import redis

app = Flask(__name__)

# Configuration
JWT_SECRET = os.environ.get('JWT_SECRET', secrets.token_hex(32))
JWT_ALGORITHM = 'HS256'
JWT_ACCESS_TOKEN_EXPIRES = timedelta(minutes=15)
JWT_REFRESH_TOKEN_EXPIRES = timedelta(days=7)

# Redis for token blacklist (use in-memory dict for demo)
redis_client = redis.Redis(host='localhost', port=6379, db=0)
token_blacklist = set()
refresh_tokens = {}

class TokenManager:
    @staticmethod
    def generate_tokens(user):
        """Generate access and refresh tokens"""
        payload = {
            'sub': user['id'],
            'username': user['username'],
            'role': user['role'],
            'iat': datetime.utcnow(),
            'exp': datetime.utcnow() + JWT_ACCESS_TOKEN_EXPIRES,
            'iss': 'secure-app',
            'aud': 'secure-app-users',
            'jti': secrets.token_hex(16)
        }
        
        access_token = jwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM)
        refresh_token = secrets.token_hex(32)
        
        # Store refresh token
        refresh_tokens[refresh_token] = {
            'user_id': user['id'],
            'expires_at': datetime.utcnow() + JWT_REFRESH_TOKEN_EXPIRES
        }
        
        return access_token, refresh_token
    
    @staticmethod
    def verify_token(token):
        """Verify and decode access token"""
        try:
            payload = jwt.decode(
                token, 
                JWT_SECRET, 
                algorithms=[JWT_ALGORITHM],
                issuer='secure-app',
                audience='secure-app-users'
            )
            
            # Check if token is blacklisted
            if payload['jti'] in token_blacklist:
                raise jwt.InvalidTokenError('Token has been revoked')
                
            return payload
        except jwt.ExpiredSignatureError:
            raise jwt.InvalidTokenError('Token has expired')
        except jwt.InvalidTokenError:
            raise jwt.InvalidTokenError('Invalid token')
    
    @staticmethod
    def blacklist_token(jti):
        """Add token to blacklist"""
        token_blacklist.add(jti)
        # In production, store in Redis with expiration
        
    @staticmethod
    def verify_refresh_token(refresh_token):
        """Verify refresh token"""
        if refresh_token not in refresh_tokens:
            return None
            
        token_data = refresh_tokens[refresh_token]
        
        if datetime.utcnow() > token_data['expires_at']:
            del refresh_tokens[refresh_token]
            return None
            
        return token_data

def token_required(f):
    """Decorator to require valid JWT token"""
    def decorated(*args, **kwargs):
        token = request.headers.get('Authorization')
        
        if not token:
            return jsonify({'error': 'Token is missing'}), 401
        
        try:
            token = token.split(' ')[1]  # Remove 'Bearer ' prefix
            payload = TokenManager.verify_token(token)
            request.current_user = payload
        except jwt.InvalidTokenError as e:
            return jsonify({'error': str(e)}), 401
        
        return f(*args, **kwargs)
    
    decorated.__name__ = f.__name__
    return decorated

@app.route('/auth/login', methods=['POST'])
def login():
    """User login endpoint"""
    data = request.get_json()
    username = data.get('username')
    password = data.get('password')
    
    if not username or not password:
        return jsonify({'error': 'Username and password required'}), 400
    
    # Validate user credentials (replace with your user validation)
    user = validate_user(username, password)
    if not user:
        return jsonify({'error': 'Invalid credentials'}), 401
    
    access_token, refresh_token = TokenManager.generate_tokens(user)
    
    response = make_response(jsonify({
        'access_token': access_token,
        'user': {
            'id': user['id'],
            'username': user['username'],
            'role': user['role']
        }
    }))
    
    # Set refresh token as httpOnly cookie
    response.set_cookie(
        'refresh_token',
        refresh_token,
        httponly=True,
        secure=True,  # Use HTTPS in production
        samesite='Strict',
        max_age=int(JWT_REFRESH_TOKEN_EXPIRES.total_seconds())
    )
    
    return response

@app.route('/auth/refresh', methods=['POST'])
def refresh():
    """Refresh access token"""
    refresh_token = request.cookies.get('refresh_token')
    
    if not refresh_token:
        return jsonify({'error': 'Refresh token required'}), 401
    
    token_data = TokenManager.verify_refresh_token(refresh_token)
    
    if not token_data:
        return jsonify({'error': 'Invalid or expired refresh token'}), 401
    
    user = get_user_by_id(token_data['user_id'])
    access_token, new_refresh_token = TokenManager.generate_tokens(user)
    
    # Remove old refresh token
    del refresh_tokens[refresh_token]
    
    response = make_response(jsonify({
        'access_token': access_token
    }))
    
    # Set new refresh token
    response.set_cookie(
        'refresh_token',
        new_refresh_token,
        httponly=True,
        secure=True,
        samesite='Strict',
        max_age=int(JWT_REFRESH_TOKEN_EXPIRES.total_seconds())
    )
    
    return response

@app.route('/auth/logout', methods=['POST'])
@token_required
def logout():
    """User logout endpoint"""
    # Blacklist the access token
    TokenManager.blacklist_token(request.current_user['jti'])
    
    # Remove refresh token
    refresh_token = request.cookies.get('refresh_token')
    if refresh_token and refresh_token in refresh_tokens:
        del refresh_tokens[refresh_token]
    
    response = make_response(jsonify({'message': 'Logged out successfully'}))
    response.set_cookie('refresh_token', '', expires=0)
    
    return response

@app.route('/protected', methods=['GET'])
@token_required
def protected():
    """Protected route example"""
    return jsonify({
        'message': 'This is a protected route',
        'user': request.current_user
    })

def validate_user(username, password):
    """Validate user credentials (replace with your implementation)"""
    # This is a mock function - replace with your actual user validation
    users = {
        'admin': {'id': 1, 'username': 'admin', 'password': 'hashed_password', 'role': 'admin'},
        'user': {'id': 2, 'username': 'user', 'password': 'hashed_password', 'role': 'user'}
    }
    
    user = users.get(username)
    if user and bcrypt.checkpw(password.encode('utf-8'), user['password'].encode('utf-8')):
        return user
    return None

def get_user_by_id(user_id):
    """Get user by ID (replace with your implementation)"""
    # Mock function - replace with your actual user retrieval
    users = {
        1: {'id': 1, 'username': 'admin', 'role': 'admin'},
        2: {'id': 2, 'username': 'user', 'role': 'user'}
    }
    return users.get(user_id)

if __name__ == '__main__':
    app.run(debug=True)

Java Implementation (Spring Boot)

@RestController
@RequestMapping("/auth")
public class AuthController {
    
    @Autowired
    private JwtTokenProvider tokenProvider;
    
    @Autowired
    private UserService userService;
    
    @PostMapping("/login")
    public ResponseEntity login(@RequestBody LoginRequest loginRequest) {
        try {
            User user = userService.validateUser(
                loginRequest.getUsername(), 
                loginRequest.getPassword()
            );
            
            if (user == null) {
                return ResponseEntity.status(401)
                    .body(new AuthResponse("Invalid credentials"));
            }
            
            String accessToken = tokenProvider.generateAccessToken(user);
            String refreshToken = tokenProvider.generateRefreshToken(user);
            
            // Set refresh token as HttpOnly cookie
            ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", refreshToken)
                .httpOnly(true)
                .secure(true)
                .sameSite("Strict")
                .maxAge(Duration.ofDays(7))
                .build();
            
            return ResponseEntity.ok()
                .header(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString())
                .body(new AuthResponse("Login successful", accessToken, user));
                
        } catch (Exception e) {
            return ResponseEntity.status(500)
                .body(new AuthResponse("Login failed"));
        }
    }
    
    @PostMapping("/refresh")
    public ResponseEntity refresh(HttpServletRequest request) {
        String refreshToken = getRefreshTokenFromCookie(request);
        
        if (refreshToken == null || !tokenProvider.validateRefreshToken(refreshToken)) {
            return ResponseEntity.status(401)
                .body(new AuthResponse("Invalid refresh token"));
        }
        
        User user = tokenProvider.getUserFromRefreshToken(refreshToken);
        String newAccessToken = tokenProvider.generateAccessToken(user);
        String newRefreshToken = tokenProvider.generateRefreshToken(user);
        
        // Revoke old refresh token
        tokenProvider.revokeRefreshToken(refreshToken);
        
        ResponseCookie refreshTokenCookie = ResponseCookie.from("refreshToken", newRefreshToken)
            .httpOnly(true)
            .secure(true)
            .sameSite("Strict")
            .maxAge(Duration.ofDays(7))
            .build();
        
        return ResponseEntity.ok()
            .header(HttpHeaders.SET_COOKIE, refreshTokenCookie.toString())
            .body(new AuthResponse("Token refreshed", newAccessToken));
    }
    
    @PostMapping("/logout")
    public ResponseEntity logout(HttpServletRequest request) {
        String accessToken = getAccessTokenFromHeader(request);
        String refreshToken = getRefreshTokenFromCookie(request);
        
        if (accessToken != null) {
            tokenProvider.revokeAccessToken(accessToken);
        }
        
        if (refreshToken != null) {
            tokenProvider.revokeRefreshToken(refreshToken);
        }
        
        ResponseCookie deleteRefreshTokenCookie = ResponseCookie.from("refreshToken", "")
            .httpOnly(true)
            .secure(true)
            .sameSite("Strict")
            .maxAge(0)
            .build();
        
        return ResponseEntity.ok()
            .header(HttpHeaders.SET_COOKIE, deleteRefreshTokenCookie.toString())
            .body(new AuthResponse("Logged out successfully"));
    }
}

Handling Token Lifecycle

Token Generation Best Practices

  • Always include an expiration time (exp claim)
  • Use short expiration times for access tokens (15-30 minutes)
  • Include necessary claims only (principle of least privilege)
  • Use strong, unique JWT IDs (jti) for each token
  • Include issuer (iss) and audience (aud) claims

Token Storage on Client Side

Web Applications

Storage Method Pros Cons Best For
HttpOnly Cookies XSS protection, automatic sending CSRF vulnerability, size limits Traditional web apps
localStorage Large storage, explicit control XSS vulnerable, persistent SPAs with XSS protection
sessionStorage Tab-scoped, automatic cleanup XSS vulnerable, lost on close Temporary sessions
Memory only Most secure, no persistence Lost on refresh, complex to manage High-security applications

Mobile Applications

  • iOS: Use Keychain Services for secure storage
  • Android: Use Android Keystore system
  • React Native: Use @react-native-async-storage/async-storage with encryption
  • Flutter: Use flutter_secure_storage package

Token Refresh Strategy

Automatic Refresh

// Automatic token refresh implementation
class TokenManager {
    constructor() {
        this.accessToken = null;
        this.refreshToken = null;
        this.refreshTimer = null;
    }
    
    setTokens(accessToken, refreshToken) {
        this.accessToken = accessToken;
        this.refreshToken = refreshToken;
        this.scheduleRefresh();
    }
    
    scheduleRefresh() {
        if (this.refreshTimer) {
            clearTimeout(this.refreshTimer);
        }
        
        // Decode token to get expiration
        const payload = JSON.parse(atob(this.accessToken.split('.')[1]));
        const expiresAt = payload.exp * 1000; // Convert to milliseconds
        const now = Date.now();
        
        // Refresh 5 minutes before expiration
        const refreshAt = expiresAt - (5 * 60 * 1000);
        const timeUntilRefresh = refreshAt - now;
        
        if (timeUntilRefresh > 0) {
            this.refreshTimer = setTimeout(() => {
                this.refreshTokens();
            }, timeUntilRefresh);
        }
    }
    
    async refreshTokens() {
        try {
            const response = await fetch('/auth/refresh', {
                method: 'POST',
                credentials: 'include' // Include cookies
            });
            
            if (response.ok) {
                const data = await response.json();
                this.setTokens(data.accessToken, this.refreshToken);
            } else {
                // Refresh failed, redirect to login
                this.logout();
            }
        } catch (error) {
            console.error('Token refresh failed:', error);
            this.logout();
        }
    }
    
    async makeAuthenticatedRequest(url, options = {}) {
        const response = await fetch(url, {
            ...options,
            headers: {
                ...options.headers,
                'Authorization': `Bearer ${this.accessToken}`
            }
        });
        
        if (response.status === 401) {
            // Token might be expired, try to refresh
            await this.refreshTokens();
            
            // Retry the request
            return fetch(url, {
                ...options,
                headers: {
                    ...options.headers,
                    'Authorization': `Bearer ${this.accessToken}`
                }
            });
        }
        
        return response;
    }
    
    logout() {
        this.accessToken = null;
        this.refreshToken = null;
        if (this.refreshTimer) {
            clearTimeout(this.refreshTimer);
        }
        
        // Redirect to login page
        window.location.href = '/login';
    }
}

Token Revocation

Immediate Revocation

// Token blacklist implementation with Redis
const redis = require('redis');
const client = redis.createClient();

class TokenBlacklist {
    static async addToBlacklist(jti, exp) {
        const ttl = exp - Math.floor(Date.now() / 1000);
        if (ttl > 0) {
            await client.setex(`blacklist:${jti}`, ttl, 'revoked');
        }
    }
    
    static async isBlacklisted(jti) {
        const result = await client.get(`blacklist:${jti}`);
        return result === 'revoked';
    }
    
    static async revokeAllUserTokens(userId) {
        // Add user to global revocation list
        await client.set(`user_revoked:${userId}`, Date.now());
    }
    
    static async isUserRevoked(userId, tokenIssuedAt) {
        const revokedAt = await client.get(`user_revoked:${userId}`);
        if (revokedAt) {
            return parseInt(revokedAt) > tokenIssuedAt * 1000;
        }
        return false;
    }
}

// Middleware to check token validity
async function validateToken(req, res, next) {
    try {
        const token = req.headers.authorization?.split(' ')[1];
        const decoded = jwt.verify(token, JWT_SECRET);
        
        // Check if token is blacklisted
        if (await TokenBlacklist.isBlacklisted(decoded.jti)) {
            return res.status(401).json({ error: 'Token has been revoked' });
        }
        
        // Check if user has been globally revoked
        if (await TokenBlacklist.isUserRevoked(decoded.sub, decoded.iat)) {
            return res.status(401).json({ error: 'User tokens have been revoked' });
        }
        
        req.user = decoded;
        next();
    } catch (err) {
        res.status(401).json({ error: 'Invalid token' });
    }
}

Advanced Security Measures

1. JSON Web Encryption (JWE)

For highly sensitive data, use JWE to encrypt the entire JWT payload:

// JWE implementation using jose library
const { EncryptJWT, jwtDecrypt } = require('jose');

const secret = new TextEncoder().encode(process.env.JWE_SECRET);

async function createEncryptedJWT(payload) {
    const jwt = await new EncryptJWT(payload)
        .setProtectedHeader({ alg: 'A256KW', enc: 'A256GCM' })
        .setIssuedAt()
        .setExpirationTime('2h')
        .setIssuer('secure-app')
        .setAudience('secure-app-users')
        .encrypt(secret);
    
    return jwt;
}

async function decryptJWT(encryptedJWT) {
    const { payload } = await jwtDecrypt(encryptedJWT, secret, {
        issuer: 'secure-app',
        audience: 'secure-app-users'
    });
    
    return payload;
}

2. Rate Limiting

Implement rate limiting to prevent brute force attacks:

const rateLimit = require('express-rate-limit');

// Rate limiting for authentication endpoints
const authLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 5, // Limit each IP to 5 requests per windowMs
    message: 'Too many login attempts, please try again later',
    standardHeaders: true,
    legacyHeaders: false,
    skipSuccessfulRequests: true
});

// Apply rate limiting to auth routes
app.use('/auth/login', authLimiter);
app.use('/auth/refresh', authLimiter);

3. Device Fingerprinting

Bind tokens to specific devices for additional security:

// Device fingerprinting
function generateDeviceFingerprint(req) {
    const userAgent = req.headers['user-agent'];
    const acceptLanguage = req.headers['accept-language'];
    const acceptEncoding = req.headers['accept-encoding'];
    
    const fingerprint = crypto
        .createHash('sha256')
        .update(`${userAgent}${acceptLanguage}${acceptEncoding}`)
        .digest('hex');
    
    return fingerprint;
}

// Include device fingerprint in token
function generateTokenWithFingerprint(user, req) {
    const deviceFingerprint = generateDeviceFingerprint(req);
    
    const payload = {
        sub: user.id,
        username: user.username,
        role: user.role,
        device: deviceFingerprint
    };
    
    return jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
}

// Validate device fingerprint
function validateDeviceFingerprint(req, res, next) {
    const currentFingerprint = generateDeviceFingerprint(req);
    const tokenFingerprint = req.user.device;
    
    if (currentFingerprint !== tokenFingerprint) {
        return res.status(401).json({ error: 'Device fingerprint mismatch' });
    }
    
    next();
}

4. IP Address Validation

Bind tokens to specific IP addresses:

// IP address validation
function getClientIP(req) {
    return req.headers['x-forwarded-for'] || 
           req.connection.remoteAddress || 
           req.socket.remoteAddress ||
           (req.connection.socket ? req.connection.socket.remoteAddress : null);
}

// Include IP in token
function generateTokenWithIP(user, req) {
    const clientIP = getClientIP(req);
    
    const payload = {
        sub: user.id,
        username: user.username,
        role: user.role,
        ip: clientIP
    };
    
    return jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
}

// Validate IP address
function validateIPAddress(req, res, next) {
    const currentIP = getClientIP(req);
    const tokenIP = req.user.ip;
    
    if (currentIP !== tokenIP) {
        return res.status(401).json({ error: 'IP address mismatch' });
    }
    
    next();
}

5. Multi-Factor Authentication (MFA)

Implement MFA for sensitive operations:

// MFA implementation
const speakeasy = require('speakeasy');

// Generate MFA token
function generateMFAToken(user) {
    const payload = {
        sub: user.id,
        username: user.username,
        role: user.role,
        mfa_verified: false
    };
    
    return jwt.sign(payload, JWT_SECRET, { expiresIn: '5m' });
}

// Verify MFA and upgrade token
app.post('/auth/mfa-verify', authenticateToken, (req, res) => {
    const { mfaCode } = req.body;
    
    // Verify MFA code
    const verified = speakeasy.totp.verify({
        secret: req.user.mfa_secret,
        encoding: 'base32',
        token: mfaCode,
        window: 2
    });
    
    if (!verified) {
        return res.status(401).json({ error: 'Invalid MFA code' });
    }
    
    // Generate new token with MFA verified
    const payload = {
        ...req.user,
        mfa_verified: true
    };
    
    const newToken = jwt.sign(payload, JWT_SECRET, { expiresIn: '15m' });
    
    res.json({ accessToken: newToken });
});

// Middleware to require MFA for sensitive operations
function requireMFA(req, res, next) {
    if (!req.user.mfa_verified) {
        return res.status(403).json({ error: 'MFA verification required' });
    }
    next();
}

Common Implementation Mistakes

1. Storing Sensitive Data in JWT

Problem

Including sensitive information in JWT payloads.

Bad Example

{
  "sub": "user123",
  "password": "hashedPassword",
  "creditCard": "1234-5678-9012-3456",
  "ssn": "123-45-6789"
}

Good Example

{
  "sub": "user123",
  "role": "user",
  "permissions": ["read", "write"]
}

2. Using Weak Secrets

Problem

Using predictable or weak secrets for HMAC signing.

Bad Example

const JWT_SECRET = 'secret';
const JWT_SECRET = 'myapp';
const JWT_SECRET = '123456';

Good Example

const JWT_SECRET = crypto.randomBytes(32).toString('hex');
// Or use environment variables with strong secrets
const JWT_SECRET = process.env.JWT_SECRET;

3. Not Validating Claims

Problem

Skipping validation of important claims like audience, issuer, and expiration.

Bad Example

// Minimal validation
const decoded = jwt.verify(token, secret);

Good Example

// Comprehensive validation
const decoded = jwt.verify(token, secret, {
    algorithms: ['HS256'],
    issuer: 'my-app',
    audience: 'my-api',
    maxAge: '15m'
});

4. Long Token Expiration

Problem

Setting very long expiration times for access tokens.

Bad Example

// Token valid for 30 days
const token = jwt.sign(payload, secret, { expiresIn: '30d' });

Good Example

// Short-lived access token with refresh mechanism
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });
const refreshToken = crypto.randomBytes(32).toString('hex');

5. Ignoring Algorithm Validation

Problem

Not specifying allowed algorithms, leaving room for algorithm confusion attacks.

Bad Example

// Accepts any algorithm
const decoded = jwt.verify(token, secret);

Good Example

// Explicitly specify allowed algorithms
const decoded = jwt.verify(token, secret, { 
    algorithms: ['HS256'] 
});

JWT in Microservices Architecture

Challenges in Microservices

  • Token validation across multiple services
  • Service-to-service authentication
  • Token propagation and context sharing
  • Consistent security policies

Architecture Patterns

1. Centralized Authentication Service

// API Gateway with JWT validation
const express = require('express');
const jwt = require('jsonwebtoken');
const httpProxy = require('http-proxy-middleware');

const app = express();

// JWT validation middleware
function validateJWT(req, res, next) {
    const token = req.headers.authorization?.split(' ')[1];
    
    if (!token) {
        return res.status(401).json({ error: 'No token provided' });
    }
    
    try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET);
        req.user = decoded;
        
        // Add user context to forwarded request
        req.headers['x-user-id'] = decoded.sub;
        req.headers['x-user-role'] = decoded.role;
        
        next();
    } catch (err) {
        return res.status(401).json({ error: 'Invalid token' });
    }
}

// Proxy configuration
const services = {
    '/user': 'http://user-service:3001',
    '/product': 'http://product-service:3002',
    '/order': 'http://order-service:3003'
};

// Apply JWT validation to all routes
app.use(validateJWT);

// Proxy requests to microservices
Object.keys(services).forEach(path => {
    app.use(path, httpProxy({
        target: services[path],
        changeOrigin: true,
        pathRewrite: { [`^${path}`]: '' }
    }));
});

app.listen(3000, () => {
    console.log('API Gateway running on port 3000');
});

2. Service-to-Service Authentication

// Service-to-service JWT
class ServiceAuthenticator {
    constructor(serviceName, serviceSecret) {
        this.serviceName = serviceName;
        this.serviceSecret = serviceSecret;
    }
    
    generateServiceToken(targetService, scopes = []) {
        const payload = {
            sub: this.serviceName,
            aud: targetService,
            iss: 'service-mesh',
            scope: scopes,
            type: 'service'
        };
        
        return jwt.sign(payload, this.serviceSecret, { expiresIn: '5m' });
    }
    
    validateServiceToken(token, expectedAudience) {
        try {
            const decoded = jwt.verify(token, this.serviceSecret, {
                audience: expectedAudience,
                issuer: 'service-mesh'
            });
            
            if (decoded.type !== 'service') {
                throw new Error('Invalid token type');
            }
            
            return decoded;
        } catch (err) {
            throw new Error('Invalid service token');
        }
    }
}

// Usage in microservice
const serviceAuth = new ServiceAuthenticator('user-service', process.env.SERVICE_SECRET);

// Making authenticated request to another service
async function callOrderService(userId) {
    const token = serviceAuth.generateServiceToken('order-service', ['read:orders']);
    
    const response = await fetch('http://order-service/orders', {
        headers: {
            'Authorization': `Bearer ${token}`,
            'X-User-ID': userId
        }
    });
    
    return response.json();
}

Best Practices for Microservices

1. Token Propagation

// Middleware to propagate user context
function propagateUserContext(req, res, next) {
    if (req.user) {
        // Add user information to request headers
        req.headers['x-user-id'] = req.user.sub;
        req.headers['x-user-role'] = req.user.role;
        req.headers['x-user-permissions'] = JSON.stringify(req.user.permissions);
    }
    next();
}

// Service client with context propagation
class ServiceClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    async request(path, options = {}, userContext = null) {
        const headers = {
            'Content-Type': 'application/json',
            ...options.headers
        };
        
        // Propagate user context if available
        if (userContext) {
            headers['x-user-id'] = userContext.sub;
            headers['x-user-role'] = userContext.role;
            headers['x-user-permissions'] = JSON.stringify(userContext.permissions);
        }
        
        const response = await fetch(`${this.baseUrl}${path}`, {
            ...options,
            headers
        });
        
        return response.json();
    }
}

2. Distributed Token Validation

// Shared JWT validation service
class DistributedJWTValidator {
    constructor(publicKey, redisClient) {
        this.publicKey = publicKey;
        this.redis = redisClient;
    }
    
    async validateToken(token) {
        try {
            // First check cache
            const cacheKey = `jwt:${token}`;
            const cachedResult = await this.redis.get(cacheKey);
            
            if (cachedResult) {
                return JSON.parse(cachedResult);
            }
            
            // Validate token
            const decoded = jwt.verify(token, this.publicKey, {
                algorithms: ['RS256'],
                issuer: 'auth-service'
            });
            
            // Check if token is blacklisted
            const isBlacklisted = await this.redis.sismember('blacklist', decoded.jti);
            if (isBlacklisted) {
                throw new Error('Token revoked');
            }
            
            // Cache the result
            const ttl = decoded.exp - Math.floor(Date.now() / 1000);
            await this.redis.setex(cacheKey, ttl, JSON.stringify(decoded));
            
            return decoded;
        } catch (err) {
            throw new Error('Token validation failed');
        }
    }
}

// Usage in multiple services
const validator = new DistributedJWTValidator(publicKey, redisClient);

// Shared middleware
async function validateJWTMiddleware(req, res, next) {
    try {
        const token = req.headers.authorization?.split(' ')[1];
        const decoded = await validator.validateToken(token);
        req.user = decoded;
        next();
    } catch (err) {
        res.status(401).json({ error: 'Unauthorized' });
    }
}