Broken Authentication

Broken Authentication in Node.js Express - CVE-2026-33409 [CVE-2026-33409]

[Fixed 2026-03] Updated CVE-2026-33409

Overview

The CVE-2026-33409 vulnerability describes an authentication bypass in Parse Server deployments when the server option allowExpiredAuthDataToken is set to true. An attacker who learns a linked providerId could log in as the corresponding user without knowing their credentials, gaining full access to the account and a valid session token. This is particularly dangerous in multi-tenant or user-provided OAuth setups where provider IDs can be discovered or guessed. The issue is patched in Parse Server versions 8.6.52 and 9.6.0-alpha.41, with the default behavior stopping token issuance from expired or unauthenticated sessions. For Node.js applications using Express, this class of vulnerability manifests as broken authentication flow when library defaults or feature flags enable credential bypass paths. In practice, an Express-based API might indirectly inherit this risk if it relies on Parse Server’s auth behavior or mirrors the same bypass logic in custom middleware. Remediation centers on upgrading to patched releases, explicitly disabling allowExpiredAuthDataToken, and enforcing robust, credential-verified OAuth/token validation flows for any provider-based login paths.

Affected Versions

8.x: < 8.6.52; 9.x: < 9.6.0-alpha.41

Code Fix Example

Node.js (Express) API Security Remediation
/* VULNERABLE PATTERN */
const express = require('express');
const app = express();
app.use(express.json());

// Mock user store
const users = [
  { id: 'u1', provider: 'google', providerId: 'google-uid-123', passwordHash: 'pw:password123' }
];

// Simulated Parse Server config flag (vulnerable when true)
const config = { allowExpiredAuthDataToken: true };

app.post('/login-with-provider', (req, res) => {
  const { provider, providerId, password } = req.body;
  const user = users.find(u => u.provider === provider && u.providerId === providerId);
  if (!user) return res.status(401).send('Unauthorized');
  // Vulnerable: bypass credential check if allowExpiredAuthDataToken is true
  if (config.allowExpiredAuthDataToken) {
    return res.json({ sessionToken: 'sess-' + user.id });
  }
  // Insecure fallback (not reached when config flag is true)
  if (password && password === user.passwordHash.replace('pw:', '')) {
    return res.json({ sessionToken: 'sess-' + user.id });
  }
  res.status(401).send('Unauthorized');
});

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

/* FIXED PATTERN */
const expressFixed = require('express');
const appFixed = expressFixed();
appFixed.use(expressFixed.json());

const usersFixed = [
  { id: 'u1', provider: 'google', providerId: 'google-uid-123', passwordHash: 'pw:password123' }
];
// Patch: disable allowExpiredAuthDataToken
const configFixed = { allowExpiredAuthDataToken: false };

function verifyPassword(input, stored) {
  // In production use bcrypt/argon2; this is a simplified illustration
  return stored.startsWith('pw:') && input === stored.slice(3);
}

appFixed.post('/login-with-provider', (req, res) => {
  const { provider, providerId, password } = req.body;
  const user = usersFixed.find(u => u.provider === provider && u.providerId === providerId);
  if (!user) return res.status(401).send('Unauthorized');
  // Fixed: require proper credentials or a valid OAuth flow; no bypass path
  if (verifyPassword(password || '', user.passwordHash)) {
    return res.json({ sessionToken: 'sess-' + user.id });
  }
  res.status(401).send('Unauthorized');
});

appFixed.listen(3001, () => console.log('Fixed server listening on port 3001'));

CVE References

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