Broken Authentication

Broken Authentication in Node.js (Express) guide [GHSA-wvr4-3wq4-gpc5]

[Updated March 2026] Updated GHSA-wvr4-3wq4-gpc5

Overview

Broken Authentication in Node.js (Express) can have severe real-world consequences, including attackers bypassing login, impersonating users, or gaining unauthorized access to sensitive resources. When session tokens, cookies, or credentials are mishandled, attackers can hijack sessions, replay tokens, or perform credential stuffing with little risk. This guide focuses on the general class of weaknesses that commonly appear in Express apps, without referencing a specific CVE in this prompt. Understanding these patterns helps teams rapidly reduce exposure even in legacy codebases.

Code Fix Example

Node.js (Express) API Security Remediation
/* Vulnerable pattern and a secure fix shown side by side in the same snippet */

const express = require('express');
const app = express();
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

app.use(express.json());

// In-memory user store for demonstration (do not use in production)
// Vulnerable: plaintext password used for auth checks and a hard-coded secret for JWT
const users = [
  { id: 1, username: 'alice', password: 'password123', passwordHash: null }
];
// Pre-hash for demonstration purposes (would normally be stored already hashed in DB)
users[0].passwordHash = bcrypt.hashSync(users[0].password, 10);

function loginVulnerable(req, res) {
  const user = users.find(u => u.username === req.body.username && u.password === req.body.password);
  if (user) {
    // Insecure: hard-coded secret, and token is stored in a readable cookie
    const token = jwt.sign({ userId: user.id }, 'secret', { expiresIn: '1h' });
    res.cookie('session', token); // httpOnly: false, secure: false by default
    return res.json({ ok: true });
  }
  res.status(401).json({ ok: false });
}

function loginFixed(req, res) {
  // Secure: use hashed passwords, env-stored secret, and secure cookies
  const user = users.find(u => u.username === req.body.username);
  if (user && bcrypt.compareSync(req.body.password, user.passwordHash)) {
    const secret = process.env.JWT_SECRET || 'defaultDevSecret';
    const token = jwt.sign({ userId: user.id }, secret, { expiresIn: '1h' });
    res.cookie('session', token, { httpOnly: true, secure: true, sameSite: 'Strict' });
    return res.json({ ok: true });
  }
  res.status(401).json({ ok: false });
}

// Demonstration routes: vulnerable and fixed
app.post('/login/vulnerable', loginVulnerable);
app.post('/login/fixed', loginFixed);

app.listen(3000, () => console.log('Auth demo listening on port 3000'));

// Note: In production, eliminate plaintext password checks, move secrets to env vars, enable TLS, and enforce token verification on protected routes.

CVE References

Choose which optional cookies to allow. You can change this any time.