JWT Security Checklist

Essential security checklist for JWT implementation. Follow these proven practices to protect your applications from common JWT vulnerabilities and attacks.

Security Implementation Progress

0%
0 of 25 security practices implemented

🚨 Critical Security Priorities

1. Always Use HTTPS

Never transmit JWTs over unencrypted connections

2. Set Short Expiration

Limit token lifetime to reduce attack window

3. Validate Everything

Verify signature, expiration, and all claims

🔐 Token Generation Security

Enforce HTTPS Only

critical

Never generate, transmit, or accept JWT tokens over unencrypted HTTP connections.

// ❌ Never do this
http://api.example.com/login

// ✅ Always use HTTPS
https://api.example.com/login

Use Cryptographically Strong Secrets

critical

Generate secrets with at least 256 bits of entropy. Never use predictable or weak secrets.

// ✅ Generate strong secret
const secret = crypto.randomBytes(32).toString('hex');
// Result: 64-character hex string (256 bits)

Set Short Token Expiration

high

Use 15-60 minutes for access tokens. Implement refresh tokens for longer sessions.

✅ Access Tokens
15-60 minutes
✅ API Tokens
1-24 hours
✅ Refresh Tokens
7-30 days

Validate and Specify Algorithm

critical

Always specify and validate the expected signing algorithm. Prevent algorithm confusion attacks.

// ❌ Accepts any algorithm
jwt.verify(token, secret)

// ✅ Specify expected algorithm
jwt.verify(token, secret, { algorithms: ['HS256'] })

Never Store Sensitive Data in JWT

critical

JWTs are encoded, not encrypted. Never include passwords, SSNs, or other sensitive information.

❌ Never Include

  • • Passwords or password hashes
  • • Social Security Numbers
  • • Credit card information
  • • API secrets or keys
  • • Personal identification numbers

✅ Safe to Include

  • • User ID (non-sequential)
  • • Username or email
  • • User roles and permissions
  • • Non-sensitive metadata
  • • Application preferences

🔍 Token Validation Security

Always Verify Token Signature

critical

Never skip signature verification. This is your primary defense against token tampering.

// ✅ Proper verification
try {
  const decoded = jwt.verify(token, secret, { algorithms: ['HS256'] });
  // Token is valid and verified
} catch (error) {
  // Invalid token - reject request
  return res.status(401).json({ error: 'Invalid token' });
}

Validate Token Expiration

critical

Always check the 'exp' claim. Expired tokens should be immediately rejected.

// ✅ Expiration is automatically checked by most libraries
// But you can also manually verify:
if (decoded.exp && Date.now() >= decoded.exp * 1000) {
  throw new Error('Token has expired');
}

Validate Token Issuer (iss)

high

Check that tokens come from expected issuers to prevent token confusion attacks.

const decoded = jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer: 'your-app-name',
  audience: 'your-api'
});

Validate Token Audience (aud)

high

Ensure tokens are intended for your application by validating the audience claim.

// Token should specify intended audience
const payload = {
  sub: 'user123',
  aud: 'api.yourapp.com', // Your API endpoint
  iss: 'yourapp.com'
};

Validate 'Not Before' Claim (nbf)

medium

If using nbf claim, ensure tokens are not used before their valid time.

if (decoded.nbf && Date.now() < decoded.nbf * 1000) {
  throw new Error('Token not yet valid');
}

📦 Storage and Transmission Security

Use Secure Storage Methods

high

Store tokens in httpOnly cookies or secure storage. Avoid localStorage for sensitive tokens.

✅ Secure Options

  • • httpOnly cookies
  • • Secure cookies
  • • SameSite cookies
  • • Memory (temporary)

⚠️ Use with Caution

  • • localStorage (XSS risk)
  • • sessionStorage
  • • Regular cookies

❌ Never Use

  • • URL parameters
  • • Referer headers
  • • Unencrypted storage
  • • Plain text files

Use Authorization Header

high

Send tokens in Authorization header with Bearer scheme, not in URL or body.

// ✅ Correct way
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...

// ❌ Never in URL
https://api.example.com/data?token=eyJhbGc...

Implement CSRF Protection

high

Use CSRF tokens or double-submit cookie pattern when storing JWTs in cookies.

// Set secure cookie with CSRF protection
res.cookie('jwt', token, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict'
});

Configure CORS Properly

medium

Set specific origins in CORS, never use wildcards (*) for credentials.

// ✅ Specific origins
Access-Control-Allow-Origin: https://yourapp.com
Access-Control-Allow-Credentials: true

// ❌ Never with credentials
Access-Control-Allow-Origin: *

🛡️ Application Security

Implement Refresh Token Strategy

high

Use refresh tokens for long-lived sessions to minimize exposure of access tokens.

// Access token: short-lived (15-60 minutes)
const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });

// Refresh token: longer-lived (7-30 days)
const refreshToken = jwt.sign({ type: 'refresh' }, secret, { expiresIn: '7d' });

Implement Token Blacklisting

high

Maintain a blacklist for revoked tokens, especially for logout and security incidents.

// Store revoked token IDs in Redis/database
await redis.setex('blacklist:' + jti, expTime, 'true');

// Check blacklist on each request
const isBlacklisted = await redis.get('blacklist:' + decoded.jti);

Implement Rate Limiting

medium

Add rate limiting to token endpoints to prevent brute force and abuse.

Login Endpoint

  • • 5 attempts per minute per IP
  • • 10 attempts per hour per user
  • • Progressive delays

Token Refresh

  • • 10 requests per minute
  • • Monitor for unusual patterns
  • • Implement circuit breakers

Implement Security Logging

medium

Log security events for monitoring and incident response.

// Log security events
logger.warn('Invalid JWT signature', { ip, userAgent, token: token.slice(0,20) });
logger.info('Token refreshed', { userId, ip });
logger.error('Expired token used', { userId, expired: decoded.exp });

Set Security Headers

medium

Configure security headers to protect against common attacks.

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self'

🧪 Development and Testing

Secure Environment Configuration

critical

Use environment variables for secrets, never hardcode in source code.

❌ Never Do This

const secret = 'mysecretkey123';
const secret = 'hardcoded-in-repo';

✅ Always Do This

const secret = process.env.JWT_SECRET;
if (!secret) throw new Error('Missing JWT_SECRET');

Use Different Secrets Per Environment

high

Development, staging, and production should use completely different secrets.

# Development .env
JWT_SECRET=dev_secret_32_char_minimum_12345

# Production .env
JWT_SECRET=prod_different_secret_67890_xyz

# Never reuse secrets across environments

Test Security Scenarios

medium

Write tests for token validation, expiration, and attack scenarios.

// Test cases to include:
// ✓ Expired token rejection
// ✓ Invalid signature detection
// ✓ Malformed token handling
// ✓ Algorithm confusion prevention
// ✓ Missing claims validation

Regular Security Audits

medium

Conduct regular security reviews and dependency updates.

Monthly Tasks

  • • Review token lifetimes
  • • Check for library updates
  • • Analyze security logs
  • • Review access patterns

Quarterly Tasks

  • • Rotate signing secrets
  • • Security penetration testing
  • • Update security policies
  • • Team security training

Document Security Decisions

medium

Document your JWT configuration, security policies, and incident procedures.

  • • Document algorithm choices and rationale
  • • Record token lifetime decisions
  • • Maintain incident response procedures
  • • Document key rotation schedule
  • • Keep security contact information current

Your JWT Security Score

0%

🚨 Critical vulnerabilities present. Address red items immediately.

Generate Secure JWT Tokens →