← Back to Learn

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.

Algorithm Verification
// ❌ 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.

Token Storage
// ❌ 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.

Secret Generation
// ❌ 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 environment

4. No Token Expiration

⚠️ Security Risk

Tokens without expiration never expire, allowing indefinite access if compromised.

Token Expiration
// ❌ 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.

Claim Validation
// ❌ 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.

Token Transmission
// ❌ 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.

Logging
// ❌ WRONG
console.log('User token:', token);
logger.info('Request', { token });

// ✅ CORRECT
logger.info('Request', { userId: decoded.sub });
// Never log the actual token

8. Not Handling Token Revocation

⚠️ Security Gap

Without revocation, compromised tokens remain valid until expiration.

Token Revocation
// ❌ 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.

Algorithm Validation
// ❌ 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.

Header Validation
// ❌ 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.

Payload Content
// ❌ WRONG
{
  "password": "plaintext123",
  "creditCard": "1234-5678-9012",
  "ssn": "123-45-6789"
}

// ✅ CORRECT
{
  "sub": "user-id",
  "role": "user",
  "permissions": ["read"]
}
// Store sensitive data in database

12. Not Using HTTPS

❌ Man-in-the-Middle Risk

Tokens transmitted over HTTP can be intercepted and stolen.

Transport Security
// ❌ 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

Popular

Comprehensive antivirus and cybersecurity solutions for individuals and businesses. Protect your digital life with industry-leading threat detection.

Learn More

Affiliate Link

🎓

INE - Cybersecurity Training & Certification

Training

Professional cybersecurity training and certification courses. Master ethical hacking, penetration testing, and security analysis with hands-on labs.

Learn More

Affiliate Link