FuntranslatorCreate Fun Language Translations
Free
Back to Blog
February 15, 20268 min read

JWT Best Practices Checklist (Copy/Paste for Production)

A comprehensive checklist of JWT security best practices for production applications. Copy and paste to ensure your JWT implementation is secure.

Use this checklist to ensure your JWT implementation follows security best practices. Each item is critical for production security.

πŸ“‹ Quick Checklist

CategoryItemStatus
Key Management256+ bit cryptographically random secret for HS256☐
2048+ bit RSA keys for RS256☐
Different secrets per environment (dev/staging/prod)☐
Secrets stored in secure storage (not in code)☐
Token SecurityToken expiration set (exp claim)☐
Expiration time reasonable (15 min - 24 hours)☐
Issuer claim set (iss)☐
Audience claim set (aud)☐
No sensitive data in payload☐
VerificationAlgorithm explicitly specified in verify()☐
"none" algorithm rejected☐
Expiration always verified☐
Issuer and audience validated☐
TransportTokens sent via Authorization header (not URL)☐
HTTPS enforced☐
HttpOnly cookies if storing in browser☐
Key RotationRotation plan documented☐
kid claim used for multi-key support☐
Rotation tested in staging☐

πŸ” Key Management

// GOOD: Strong key generation
const crypto = require('crypto');

// HS256: 256 bits minimum
const hs256Secret = crypto.randomBytes(32).toString('hex');

// RS256: 2048 bits minimum
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
  publicKeyEncoding: { type: 'spki', format: 'pem' },
  privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

Generate secure keys: JWT Secret Generator

Environment Separation

# Development
JWT_SECRET_DEV=dev_secret_abc123...
JWT_ISSUER_DEV=myapp-dev

# Staging  
JWT_SECRET_STAGING=staging_secret_def456...
JWT_ISSUER_STAGING=myapp-staging

# Production
JWT_SECRET_PROD=prod_secret_ghi789...
JWT_ISSUER_PROD=myapp-prod

🎫 Token Security

Minimum Payload

// GOOD: Minimal payload
const token = jwt.sign({
  sub: '12345',                              // Subject (user ID)
  iat: Math.floor(Date.now() / 1000),        // Issued at
  exp: Math.floor(Date.now() / 1000) + 3600, // Expires in 1 hour
  iss: 'myapp',                              // Issuer
  aud: 'myapp-api'                           // Audience
}, secret);

// BAD: Sensitive data in payload
const badToken = jwt.sign({
  sub: '12345',
  password: 'hashed_password',  // NEVER!
  email: 'user@example.com',    // Consider carefully
  ssn: '123-45-6789'            // NEVER!
}, secret);
Token TypeRecommended TTLUse Case
Access Token15 min - 1 hourAPI access
Refresh Token7 - 30 daysGet new access tokens
Session Token24 hoursWeb sessions
Email Verification24 - 48 hoursOne-time actions
Password Reset15 - 60 minutesOne-time actions

βœ… Verification

Secure Verification Pattern

const jwt = require('jsonwebtoken');

function verifyToken(token, secret, issuer, audience) {
  try {
    return jwt.verify(token, secret, {
      algorithms: ['HS256'],          // Explicit algorithm
      issuer: issuer,                  // Validate issuer
      audience: audience,              // Validate audience
      ignoreExpiration: false,         // Always check exp
      clockTolerance: 10               // 10 second clock skew
    });
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      throw new Error('Token expired');
    } else if (err.name === 'JsonWebTokenError') {
      throw new Error('Invalid token');
    } else if (err.name === 'NotBeforeError') {
      throw new Error('Token not active');
    }
    throw err;
  }
}

Express Middleware

function authMiddleware(req, res, next) {
  const authHeader = req.headers.authorization;
  
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }
  
  const token = authHeader.split(' ')[1];
  
  try {
    req.user = verifyToken(token, process.env.JWT_SECRET, 'myapp', 'myapp-api');
    next();
  } catch (err) {
    return res.status(401).json({ error: err.message });
  }
}

// Usage
app.get('/protected', authMiddleware, (req, res) => {
  res.json({ user: req.user });
});

🚚 Transport

Client-Side (Browser)

// GOOD: Store in HttpOnly cookie (set by server)
// Server:
res.cookie('jwt', token, {
  httpOnly: true,     // Not accessible to JavaScript
  secure: true,       // HTTPS only
  sameSite: 'strict', // CSRF protection
  maxAge: 3600000     // 1 hour
});

// BAD: Store in localStorage
localStorage.setItem('jwt', token); // Vulnerable to XSS!

API Requests

// GOOD: Authorization header
fetch('/api/users', {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

// BAD: Query parameter
fetch(`/api/users?token=${token}`); // Leaks to logs!

πŸ”„ Key Rotation

Rotation Checklist

  1. Generate new key with new kid
  2. Add new key to verification (but don't activate)
  3. Deploy with both keys valid for verification
  4. Activate new key for signing
  5. Wait for grace period (longer than max token lifetime)
  6. Remove old key from verification

See detailed guide: JWT Secret Rotation Without Downtime

πŸ“Š Monitoring

What to Monitor

  • Token verification failures - Spike could indicate attack
  • Token age distribution - Many old tokens? Check client implementation
  • Key usage - Which keys are being used after rotation
  • Failed algorithm checks - Potential algorithm confusion attack

πŸ›  Testing

Test your implementation with:

Summary

Production JWT checklist:

  1. Strong keys (256+ bits) from secure random source
  2. Token expiration (15 min - 24 hours)
  3. Explicit algorithm verification
  4. No sensitive data in payload
  5. HTTPS + Authorization header
  6. HttpOnly cookies in browser
  7. Key rotation plan with kid support
  8. Monitoring and alerting

Generate secure keys: JWT Secrets Homepage

Related Articles

Common JWT Vulnerabilities and How to Prevent Them

Learn about the most common JWT security vulnerabilities including algorithm confusion, weak secrets, and token leaks. Includes prevention strategies and code examples.

Read Article

How to Rotate JWT Secrets Without Downtime (kid + Multiple Keys)

Learn how to implement zero-downtime JWT secret rotation using the kid header claim and multiple key support. Includes Node.js code examples and migration strategies.

Read Article