← Back to Blog
Security Authentication 18 min read

JWT Security Best Practices: Complete Developer Guide

Learn modern JWT security practices, token validation, attack prevention, and implementation guidelines for building secure authentication systems.

What are JSON Web Tokens (JWT)?

JSON Web Tokens (JWT) are an open standard (RFC 7519) for securely transmitting information between parties as JSON objects. JWTs are commonly used for authentication and authorization in modern web applications.

// Example JWT structure (3 parts separated by dots) eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // Header eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. // Payload SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // Signature

JWTs consist of three parts:

  1. Header: Algorithm and token type
  2. Payload: Claims (user data) and metadata
  3. Signature: Verifies token integrity

JWT Structure and Components

1. Header

Contains metadata about the token type and signing algorithm.

// Decoded Header { "alg": "HS256", // Algorithm: HMAC SHA-256 "typ": "JWT" // Type: JSON Web Token }

2. Payload (Claims)

Contains the claims (statements about an entity) and additional data.

// Decoded Payload { "sub": "1234567890", // Subject (user ID) "name": "John Doe", // Custom claim "iat": 1516239022, // Issued At (timestamp) "exp": 1516242622, // Expiration Time "iss": "dailytools.uk", // Issuer "aud": "api.dailytools.uk" // Audience }

3. Signature

Created by signing the encoded header and payload with a secret key.

// Signature creation HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )

Security Best Practices

1. Use Strong Signing Algorithms

Always use cryptographically secure algorithms:

Recommended Algorithms:
  • RS256/RS512: RSA with SHA-256/512 (asymmetric)
  • ES256/ES512: ECDSA with SHA-256/512 (asymmetric)
  • HS256/HS512: HMAC with SHA-256/512 (symmetric, requires secure key management)
Avoid These Algorithms:
  • HS256 with weak keys: Use at least 256-bit keys
  • None algorithm: Never accept unsigned tokens
  • Deprecated algorithms: MD5, SHA-1

2. Implement Proper Token Validation

Always validate tokens thoroughly before accepting them:

// Node.js example: Comprehensive JWT validation const jwt = require('jsonwebtoken'); function validateToken(token) { try { const decoded = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256', 'RS256'], // Allowed algorithms issuer: 'dailytools.uk', // Required issuer audience: 'api.dailytools.uk', // Required audience clockTolerance: 30, // 30-second clock skew tolerance maxAge: '1h' // Maximum token age }); // Additional validation if (!decoded.sub) { throw new Error('Missing subject claim'); } if (decoded.role && !['user', 'admin'].includes(decoded.role)) { throw new Error('Invalid role claim'); } return decoded; } catch (error) { console.error('JWT validation failed:', error.message); throw new Error('Invalid token'); } }

3. Set Appropriate Expiration Times

Use different expiration times based on token type:

Token Type Recommended Expiration Use Case
Access Token 15-60 minutes API access, short-lived
Refresh Token 7-30 days Obtain new access tokens
ID Token 5-10 minutes Authentication (OIDC)

Common JWT Attacks and Prevention

1. Algorithm Confusion Attack

Attackers try to switch from asymmetric to symmetric algorithm.

Attack: Change header from {"alg":"RS256"} to {"alg":"HS256"}
Prevention: Always specify allowed algorithms in validation
// Safe validation (specify algorithms) jwt.verify(token, publicKey, { algorithms: ['RS256', 'RS512'] // Only accept these algorithms }); // Unsafe validation (accepts any algorithm) jwt.verify(token, publicKey); // Vulnerable to algorithm confusion

2. None Algorithm Attack

Attackers set algorithm to "none" to bypass signature verification.

// Vulnerable library might accept: {"alg":"none","typ":"JWT"} // Prevention: Always reject "none" algorithm jwt.verify(token, secret, { algorithms: ['HS256', 'RS256'] // Explicitly list allowed algorithms });

3. JWT Secret Brute Force

Attackers attempt to guess weak HMAC secrets.

Prevention:
  • Use strong secrets (min 256-bit for HS256)
  • Consider asymmetric algorithms (RS256/ES256)
  • Implement rate limiting on token validation
  • Rotate secrets regularly

4. Token Replay Attacks

Attackers reuse valid tokens after they should be expired.

// Prevention: Use jti (JWT ID) claim and maintain revocation list { "sub": "1234567890", "iat": 1516239022, "exp": 1516242622, "jti": "unique-token-id-123" // Track token usage } // Server-side: Check if token ID has been used const usedTokens = new Set(); function isTokenReplayed(jti) { return usedTokens.has(jti); }

Implementation Guidelines

1. Token Generation

// Node.js: Safe token generation const jwt = require('jsonwebtoken'); const crypto = require('crypto'); function generateAccessToken(user) { const payload = { sub: user.id, name: user.name, email: user.email, role: user.role, iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (15 * 60), // 15 minutes iss: 'dailytools.uk', aud: 'api.dailytools.uk', jti: crypto.randomBytes(16).toString('hex') // Unique token ID }; return jwt.sign(payload, process.env.JWT_SECRET, { algorithm: 'HS256', header: { typ: 'JWT', alg: 'HS256' } }); } function generateRefreshToken(user) { const payload = { sub: user.id, token_type: 'refresh', iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60), // 7 days jti: crypto.randomBytes(32).toString('hex') }; return jwt.sign(payload, process.env.REFRESH_TOKEN_SECRET, { algorithm: 'HS256' }); }

2. Token Storage and Transmission

Important: Never store JWTs in localStorage for production applications due to XSS vulnerabilities.
// Safe storage options: // 1. HTTP-only cookies (recommended for web apps) res.cookie('access_token', token, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 15 * 60 * 1000 // 15 minutes }); // 2. Memory storage (single-page applications) // Store in memory variable, not localStorage // 3. Secure storage (mobile apps) // Use platform-specific secure storage (Keychain, Keystore)

3. Token Refresh Flow

// Secure token refresh implementation app.post('/api/refresh', async (req, res) => { const refreshToken = req.cookies.refresh_token; if (!refreshToken) { return res.status(401).json({ error: 'Refresh token required' }); } try { // Validate refresh token const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, { algorithms: ['HS256'], issuer: 'dailytools.uk' }); // Check if token is revoked const isRevoked = await checkTokenRevocation(decoded.jti); if (isRevoked) { return res.status(401).json({ error: 'Token revoked' }); } // Generate new access token const user = await getUserById(decoded.sub); const newAccessToken = generateAccessToken(user); // Set new access token in HTTP-only cookie res.cookie('access_token', newAccessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'strict', maxAge: 15 * 60 * 1000 }); res.json({ success: true }); } catch (error) { console.error('Refresh token validation failed:', error); res.status(401).json({ error: 'Invalid refresh token' }); } });

Monitoring and Logging

1. Security Logging

// Log authentication events function logAuthEvent(event, userId, details = {}) { const logEntry = { timestamp: new Date().toISOString(), event: event, userId: userId, ip: req.ip, userAgent: req.headers['user-agent'], ...details }; // Log to security monitoring system securityLogger.info(logEntry); // Alert on suspicious activity if (event === 'failed_validation' || event === 'token_replay') { alertSecurityTeam(logEntry); } } // Usage try { const decoded = validateToken(token); logAuthEvent('successful_validation', decoded.sub); } catch (error) { logAuthEvent('failed_validation', null, { error: error.message }); throw error; }

2. Rate Limiting

Prevent brute force attacks with rate limiting:

// Express rate limiting example const rateLimit = require('express-rate-limit'); const authLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // Limit each IP to 5 requests per windowMs message: 'Too many authentication attempts, please try again later', standardHeaders: true, legacyHeaders: false }); // Apply to authentication endpoints app.use('/api/login', authLimiter); app.use('/api/refresh', authLimiter);

Tools and Resources

DailyTools.uk JWT Tool

Use our JWT Parser Tool to:

  • Decode and analyze JWT tokens
  • Inspect header, payload, and signature
  • Learn about JWT structure and claims
  • Test token validation scenarios

Security Testing Tools

Conclusion

JWT security requires careful implementation of multiple layers of protection. Key takeaways:

Essential Security Practices:
  • Use strong signing algorithms (RS256/ES256 over HS256)
  • Always validate tokens with explicit algorithm lists
  • Set appropriate expiration times (short for access tokens)
  • Store tokens securely (HTTP-only cookies, not localStorage)
  • Implement proper token refresh with revocation checking
  • Add rate limiting and security monitoring

Remember that JWTs are a tool, not a complete security solution. They should be part of a comprehensive security strategy that includes input validation, output encoding, secure headers, and regular security testing.

For production applications, consider using established authentication libraries and services that have been security-audited and maintained by security experts.