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:
- Header: Algorithm and token type
- Payload: Claims (user data) and metadata
- 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.