Common JWT Implementation Mistakes
Learn about common mistakes developers make when implementing JWT authentication and how to avoid them. For professional security training, check out INE cybersecurity courses.
1. Not Verifying Algorithm
❌ Critical Mistake
This allows algorithm confusion attacks where attackers can forge tokens.
// ❌ WRONG - Accepts any algorithm
jwt.verify(token, secret);
// ✅ CORRECT - Explicitly specify algorithms
jwt.verify(token, secret, {
algorithms: ['HS256']
});2. Storing Tokens in localStorage
❌ Security Risk
localStorage is accessible to JavaScript, making tokens vulnerable to XSS attacks.
// ❌ WRONG
localStorage.setItem('token', token);
// ✅ CORRECT - Use HttpOnly cookies
res.cookie('token', token, {
httpOnly: true,
secure: true,
sameSite: 'strict'
});3. Weak Secrets
❌ Vulnerable to Brute Force
Weak secrets can be easily cracked, allowing attackers to forge tokens.
// ❌ WRONG
const secret = "secret";
const secret = "my-secret-key";
const secret = Math.random().toString(); // Weak randomness
// ✅ CORRECT
const secret = crypto.randomBytes(64).toString('hex');
const secret = process.env.JWT_SECRET; // From environment4. No Token Expiration
⚠️ Security Risk
Tokens without expiration never expire, allowing indefinite access if compromised.
// ❌ WRONG - No expiration
jwt.sign(payload, secret);
// ✅ CORRECT - Set expiration
jwt.sign(payload, secret, {
expiresIn: '15m' // 15 minutes
});5. Trusting Claims Without Validation
❌ Privilege Escalation Risk
Attackers can modify claims if they can forge tokens. Always validate against database for sensitive operations.
// ❌ WRONG - Trusts token claims
const decoded = jwt.verify(token, secret);
if (decoded.role === 'admin') {
// Grant admin access
}
// ✅ CORRECT - Verify against database
const decoded = jwt.verify(token, secret);
const user = await db.users.findById(decoded.sub);
if (user.role !== 'admin') {
throw new Error('Insufficient permissions');
}6. Passing Tokens in URLs
❌ Token Leakage Risk
Tokens in URLs get logged, cached, and leaked in Referer headers.
// ❌ WRONG
GET /api/data?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
// ✅ CORRECT
GET /api/data
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...7. Logging Tokens
❌ Token Exposure Risk
Tokens in logs can be accessed by anyone with log access.
// ❌ WRONG
console.log('User token:', token);
logger.info('Request', { token });
// ✅ CORRECT
logger.info('Request', { userId: decoded.sub });
// Never log the actual token8. Not Handling Token Revocation
⚠️ Security Gap
Without revocation, compromised tokens remain valid until expiration.
// ❌ WRONG - No revocation mechanism
// User logs out, but token still valid
// ✅ CORRECT - Implement blacklist
async function logout(token) {
const decoded = jwt.decode(token);
await redis.setex(
`blacklist:${decoded.jti}`,
decoded.exp - Math.floor(Date.now() / 1000),
'1'
);
}
async function verifyToken(token) {
const decoded = jwt.decode(token);
if (await redis.exists(`blacklist:${decoded.jti}`)) {
throw new Error('Token revoked');
}
return jwt.verify(token, secret);
}9. Using "none" Algorithm
❌ Critical Vulnerability
The "none" algorithm provides no security - anyone can modify tokens.
// ❌ WRONG - Accepts "none" algorithm
jwt.verify(token, secret);
// ✅ CORRECT - Explicitly reject "none"
jwt.verify(token, secret, {
algorithms: ['HS256', 'RS256'] // "none" not in list
});
// Also check explicitly
if (decoded.header.alg === 'none') {
throw new Error('Algorithm "none" not allowed');
}10. Not Validating Header Parameters
⚠️ Security Risk
Header parameters like kid, jku, x5u can be exploited if not validated.
// ❌ WRONG - No validation
const key = getKey(decoded.header.kid); // Path traversal possible
// ✅ CORRECT - Whitelist validation
const ALLOWED_KIDS = ['key-1', 'key-2'];
if (!ALLOWED_KIDS.includes(decoded.header.kid)) {
throw new Error('Invalid key ID');
}
const key = getKey(decoded.header.kid);11. Storing Sensitive Data in Payload
❌ Data Exposure Risk
JWT payloads are base64 encoded, not encrypted. Anyone can decode and read them.
// ❌ WRONG
{
"password": "plaintext123",
"creditCard": "1234-5678-9012",
"ssn": "123-45-6789"
}
// ✅ CORRECT
{
"sub": "user-id",
"role": "user",
"permissions": ["read"]
}
// Store sensitive data in database12. Not Using HTTPS
❌ Man-in-the-Middle Risk
Tokens transmitted over HTTP can be intercepted and stolen.
// ❌ WRONG - HTTP
http://api.example.com/auth
// ✅ CORRECT - HTTPS
https://api.example.com/auth
// Always use HTTPS in production
// Redirect HTTP to HTTPS
// Use HSTS headers✅ Quick Checklist
- ✓ Always verify algorithm explicitly
- ✓ Use HttpOnly cookies, not localStorage
- ✓ Use strong, random secrets
- ✓ Set appropriate expiration times
- ✓ Validate claims against database
- ✓ Never pass tokens in URLs
- ✓ Never log tokens
- ✓ Implement token revocation
- ✓ Never accept "none" algorithm
- ✓ Validate header parameters
- ✓ Don't store sensitive data in payload
- ✓ Always use HTTPS
Bitdefender - Advanced Cybersecurity Protection
PopularComprehensive antivirus and cybersecurity solutions for individuals and businesses. Protect your digital life with industry-leading threat detection.
Affiliate Link
INE - Cybersecurity Training & Certification
TrainingProfessional cybersecurity training and certification courses. Master ethical hacking, penetration testing, and security analysis with hands-on labs.
Affiliate Link