The Truth About JWT: When Not to Use JSON Web Tokens

JWTs are popular but often misused. Discover when to use them, when to avoid them, and how to implement proper session management in your applications.

While JWTs have become incredibly popular for authentication, they're often misused. Here's what every developer should know before implementing JWT authentication.


The Common Misconception

Many tutorials suggest using JWTs like this:

// DON'T DO THIS
const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '30d' }
);

This seems simple, but it creates several serious problems.

The Problems with JWT Sessions

1. Size Issues

// Traditional session cookie
sessionId: "abc123" // ~6 bytes

// vs JWT
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEyMywicm9sZSI6ImFkbWluIiw
iaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// ~800 bytes or more

2. Can't Invalidate Individual Tokens

// With sessions
await db.sessions.delete({ userId: 123 });
// User immediately logged out

// With JWTs
// Token remains valid until expiration
// Can't revoke without complex blacklisting

3. Security Vulnerabilities

// Common mistake: storing sensitive data
const token = jwt.sign({
  userId: user.id,
  email: user.email,        // Don't do this
  creditCard: user.ccLast4, // Really don't do this
  role: user.role
}, secret);

When to Actually Use JWTs

1. Microservices Authentication

// Service A generates token for Service B
const serviceToken = jwt.sign(
  {
    service: 'payment-processor',
    permissions: ['process-payment'],
    exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour
  },
  SERVICE_SECRET
);

2. One-Time Operations

// Password reset token
const resetToken = jwt.sign(
  {
    userId: user.id,
    purpose: 'password-reset',
    exp: Math.floor(Date.now() / 1000) + (15 * 60) // 15 minutes
  },
  RESET_SECRET
);

Better Session Management

1. Traditional Sessions

// Express session example
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 // 24 hours
  }
}));

2. Redis Session Store

const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const client = redis.createClient();

app.use(session({
  store: new RedisStore({ client }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false
}));

Real World Performance Impact

Memory Usage

1000 Active Users:
Sessions with Redis:
- Session IDs: ~6KB
- Fast lookups
- Easy to scale

JWT Solution:
- Tokens: ~800KB
- Sent with every request
- Increased bandwidth usage

Database Load

Sessions:
- One query per session validation
- Can cache in Redis

JWT:
- Must maintain blacklist for logout
- Growing blacklist size
- Complex invalidation logic

Security Best Practices

1. If You Must Use JWTs

// Minimal payload
const token = jwt.sign(
  { sub: user.id },
  process.env.JWT_SECRET,
  { 
    expiresIn: '1h',
    algorithm: 'HS256'
  }
);

// Separate user data fetch
const userData = await db.users.findById(decoded.sub);

2. Better Session Security

// Session configuration
app.use(session({
  secret: process.env.SESSION_SECRET,
  name: '__Host-sess', // Custom cookie name
  cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 1000 * 60 * 60 * 24
  }
}));

Migration Strategy

Moving from JWT to Sessions

// 1. Add session support while maintaining JWT
app.use(session(...));
app.use(jwtAuth); // Existing JWT middleware

// 2. Create sessions for JWT users
app.use(async (req, res, next) => {
  if (req.user && !req.session.userId) {
    req.session.userId = req.user.id;
  }
  next();
});

// 3. Gradually phase out JWT

Conclusion

JWTs are great for:

  • Service-to-service authentication
  • One-time tokens
  • Stateless microservices

But problematic for:

  • User sessions
  • Stateful applications
  • Systems requiring token revocation

Consider traditional sessions with Redis for better:

  • Security
  • Performance
  • Flexibility
  • Memory usage