Broken Authentication

Broken Authentication in Node.js (Express) Guide [May 2026] [GHSA-2rgp-f66f-4499]

[Updated May 2026] Updated GHSA-2rgp-f66f-4499

Overview

Broken Authentication vulnerabilities allow attackers to impersonate users or perform actions on behalf of a legitimate user due to weaknesses in how credentials, sessions, and tokens are managed. In the real world, this leads to account takeovers, access to sensitive data, and privilege escalation, particularly if admin endpoints are exposed or session tokens are long lived and poorly invalidated. In Node.js with Express this class of vulnerability often arises from misconfigured session cookies, secrets stored in code, or reliance on client side storage for tokens. Examples include express-session configured with a hard coded secret, cookies without HttpOnly or Secure flags, unrotated sessions on login, and lack of MFA or login rate limiting. Token handling with JWTs stored in localStorage or cookies without proper flags can be abused by XSS or interception. Mitigation involves secure session configuration, rotating tokens on login, using server side session storage, enabling MFA, rate limiting, CSRF protection, and keeping dependencies up to date. The guide below provides a concrete code example and actionable steps for Node.js/Express.

Code Fix Example

Node.js (Express) API Security Remediation
Vulnerable:
const express = require('express');
const session = require('express-session');
const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Vulnerable: hard-coded secret, in-memory session store, insecure defaults
app.use(session({ secret: 'keyboard cat', resave: true, saveUninitialized: true, cookie: { maxAge: 24 * 60 * 60 * 1000 } }));

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  // simplistic check (not production-ready)
  if (username === 'admin' && password === 'password') {
    req.session.user = { username };
    return res.json({ ok: true });
  }
  res.status(401).json({ ok: false });
});

app.get('/protected', (req, res) => {
  if (req.session.user) {
    res.json({ secret: 'data' });
  } else {
    res.status(401).json({ error: 'unauthorized' });
  }
});

app.listen(3000);

Fixed:
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const app = express();

const redisClient = redis.createClient({ host: '127.0.0.1', port: 6379 });

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Secure: use server-side store, rotate secret, and strict cookie settings
app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 24 * 60 * 60 * 1000
  }
}));

app.post('/login', (req, res) => {
  const { username, password } = req.body;
  if (username === 'admin' && password === 'password') {
    // rotate the session id on login to prevent session fixation
    req.session.regenerate((err) => {
      if (err) return res.status(500).json({ ok: false });
      req.session.user = { username };
      res.json({ ok: true });
    });
    return;
  }
  res.status(401).json({ ok: false });
});

function ensureAuth(req, res, next) {
  if (req.session && req.session.user) {
    return next();
  }
  res.status(401).json({ error: 'unauthorized' });
}

app.get('/protected', ensureAuth, (req, res) => {
  res.json({ secret: 'data' });
});

app.post('/logout', (req, res) => {
  req.session.destroy(() => {
    res.clearCookie('connect.sid');
    res.json({ ok: true });
  });
});

app.listen(3000);

CVE References

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