Back to Blog
high SEVERITY8 min read

How Missing Authentication Happens in Express.js APIs and How to Fix It

Four Express.js API endpoints in `index.js` — `/api/config`, `/api/subscriptions`, `/api/sites`, and `/api/refresh` — were fully accessible without any authentication, allowing any remote attacker to retrieve sensitive application data. The fix introduces both an API key authentication middleware and CSRF token protection, ensuring only authorized clients can interact with these endpoints. This is a common but critical oversight in Node.js web services that can expose configuration secrets and s

O
By Orbis AppSec
Published August 26, 2026Reviewed August 26, 2026

Answer Summary

This vulnerability is a missing authentication control (CWE-306) in an Express.js application where four API endpoints (`/api/config`, `/api/subscriptions`, `/api/sites`, `/api/refresh`) in `index.js` had no credential checks whatsoever. Any unauthenticated HTTP request could retrieve sensitive application data. The fix adds an `apiAuth` middleware that validates an `x-api-key` header against an environment variable, plus a `csrfProtect` middleware using the `csrf` package to block cross-site request forgery on state-changing requests. Both middlewares are applied globally to the `/api` route prefix.

Vulnerability at a Glance

cweCWE-306
fixAdded `apiAuth` middleware (API key check) and `csrfProtect` middleware (CSRF token validation) applied to all `/api` routes
riskAny unauthenticated remote attacker can access sensitive configuration, subscription, and site data
languageJavaScript (Node.js)
root causeExpress.js route handlers for `/api/*` endpoints registered with no authentication middleware
vulnerabilityMissing Authentication for Critical Function

The Problem With Unguarded API Routes

The index.js file in this Express.js application acts as the central routing hub, exposing endpoints that serve configuration data, subscription links, and site metadata. But when the Orbis AppSec scanner analyzed the file, it found something alarming: every single one of these endpoints — /api/config, /api/subscriptions, /api/sites, and /api/refresh — was reachable by anyone on the internet, no credentials required.

This isn't a subtle logic flaw or a tricky edge case. It's a straightforward omission: the route handlers were registered with app.get('/api/config', (req, res) => { ... }) and nothing else. No middleware. No token check. No session validation. Just open doors.

For developers building internal tools or prototypes, this pattern is easy to fall into — you add routes quickly, plan to "add auth later," and later never comes. In production, the consequences can be severe.


The Vulnerability Explained

What Was Actually Exposed

Looking at the diff, the original route registrations were clean and simple — and completely unprotected:

// BEFORE — no authentication whatsoever
app.get('/api/config', (req, res) => {
  try {
    const publicConfig = {
      sites: config.sites.map(site => ({
        // ... site configuration data
      }))
    };
    // ...
  }
});

app.get('/api/subscriptions', (req, res) => {
  try {
    const subscriptionsData = {};
    // reads JSON files from dataDir and returns them
    sites.forEach(site => {
      const siteData = fs.readJsonSync(path.join(dataDir, site));
      // ...
    });
  }
});

There is no authMiddleware, no passport.authenticate(), no req.session check — nothing between the incoming HTTP request and the response handler.

The Attack Is Trivially Simple

An attacker doesn't need to exploit a buffer overflow or craft a malicious payload. They just send a GET request:

curl http://target-host:3000/api/config
curl http://target-host:3000/api/subscriptions

That's it. Within milliseconds, they receive:
- /api/config: Application configuration including site URLs, schedule settings, and structural metadata
- /api/subscriptions: Full subscription link data read from JSON files in dataDir
- /api/sites: Site enumeration data
- /api/refresh: Potentially triggers a scraper refresh cycle

The /api/subscriptions endpoint is particularly sensitive — it reads .json files from a data directory using fs.readJsonSync() and returns their contents. Depending on what those files contain, this could expose user data, API tokens stored in config files, or internal service URLs.

Why This Is CWE-306

This vulnerability maps directly to CWE-306: Missing Authentication for Critical Function. The application performs sensitive operations (reading config, returning subscription data, triggering scraper jobs) without establishing who is making the request. There's no identity check at any layer of the request pipeline.

The scanner flagged this as CRITICAL because:
1. It's directly exploitable with zero prerequisites
2. The application is a web service — remote attackers can reach it
3. The data returned includes application internals that could enable further attacks


The Fix

The fix introduces two distinct security controls, applied at different layers of the request pipeline.

Control 1: API Key Authentication Middleware

// AFTER — apiAuth middleware added
const apiAuth = (req, res, next) => {
  const apiKey = process.env.API_KEY;
  if (!apiKey) return next(); // graceful degradation if not configured
  const token = req.headers['x-api-key'] || req.query.api_key;
  if (token !== apiKey) return res.status(401).json({ error: 'Unauthorized' });
  next();
};

This middleware:
- Reads the expected key from process.env.API_KEY (never hardcoded)
- Checks both the x-api-key header and api_key query parameter for flexibility
- Returns 401 Unauthorized if the token doesn't match
- Gracefully skips the check if API_KEY isn't set (useful during local development)

The middleware is then applied directly to each sensitive route:

// BEFORE
app.get('/api/config', (req, res) => { ... });
app.get('/api/subscriptions', (req, res) => { ... });

// AFTER
app.get('/api/config', apiAuth, (req, res) => { ... });
app.get('/api/subscriptions', apiAuth, (req, res) => { ... });

By inserting apiAuth as the second argument to app.get(), Express will call it before the route handler. If apiAuth calls res.status(401).json(...), the route handler never executes.

Control 2: CSRF Token Protection

The fix also adds CSRF protection using the csrf npm package, which guards against cross-site request forgery on state-changing requests:

const csrfLib = new Csrf();
const csrfSecret = crypto.randomBytes(18).toString('base64');

const csrfProtect = (req, res, next) => {
  if (req.method === 'GET') return next(); // GETs are safe
  const token = req.headers['x-csrf-token'];
  if (!token || !csrfLib.verify(csrfSecret, token)) {
    return res.status(403).json({ error: 'Invalid CSRF token' });
  }
  next();
};

app.get('/api/csrf-token', (req, res) => {
  res.json({ csrfToken: csrfLib.create(csrfSecret) });
});

app.use('/api', csrfProtect); // applied to ALL /api routes

A few design decisions worth noting:
- crypto.randomBytes(18).toString('base64') generates a cryptographically random secret at startup — not a hardcoded string
- CSRF checks are skipped for GET requests (which should be idempotent and non-state-changing)
- The /api/csrf-token endpoint lets legitimate frontend clients obtain a token before making POST/PUT/DELETE requests
- app.use('/api', csrfProtect) applies the middleware to every sub-route under /api in one line

The Path Traversal Bonus Fix

The diff also reveals a secondary fix in the /api/subscriptions handler:

// BEFORE — potential path traversal
const siteData = fs.readJsonSync(path.join(dataDir, site));
const siteName = site.replace('.json', '');

// AFTER — sanitized filename
const safeFile = path.basename(site);

By applying path.basename() before passing the filename to path.join(), the fix strips any directory traversal sequences like ../../etc/passwd that might appear in the site variable. This is a defense-in-depth improvement on top of the authentication fix.


Prevention & Best Practices

1. Apply Auth Middleware at the Router Level

Instead of adding apiAuth to every individual route, mount it on the router prefix:

const apiRouter = express.Router();
apiRouter.use(apiAuth); // applies to ALL routes on this router

apiRouter.get('/config', (req, res) => { ... });
apiRouter.get('/subscriptions', (req, res) => { ... });

app.use('/api', apiRouter);

This prevents accidentally forgetting to add apiAuth to a new route.

2. Never Rely on CORS Alone

CORS headers are browser-enforced only. curl, Python's requests, Postman, and any server-side HTTP client will completely ignore Access-Control-Allow-Origin. Always pair CORS with real authentication.

3. Store Secrets in Environment Variables

The fix correctly reads process.env.API_KEY rather than hardcoding a value. Use a secrets manager (AWS Secrets Manager, HashiCorp Vault, or even a .env file excluded from version control) for production deployments.

4. Audit All Route Registrations

Run a quick grep on your codebase to find unprotected routes:

grep -n "app\.get\|app\.post\|app\.put\|app\.delete" index.js | grep -v "apiAuth\|authMiddleware\|authenticate"

Any line that doesn't reference an auth middleware is a candidate for review.

5. OWASP & Standards References

This vulnerability falls under:
- OWASP API Security Top 10 — API2:2023: Broken Authentication
- OWASP Top 10 — A07:2021: Identification and Authentication Failures
- CWE-306: Missing Authentication for Critical Function


Key Takeaways

  • /api/config, /api/subscriptions, /api/sites, and /api/refresh in index.js were all publicly accessible — a single omission of middleware exposed the entire API surface
  • The apiAuth middleware pattern (check process.env.API_KEY against req.headers['x-api-key']) is a minimal, effective guard for internal APIs that don't need full OAuth/JWT infrastructure
  • app.use('/api', csrfProtect) is more reliable than per-route CSRF — applying middleware at the prefix level means new routes are protected automatically
  • path.basename() is a one-line fix for path traversal in file-reading routes — always sanitize filenames derived from request data before passing them to fs functions
  • Graceful degradation (if (!apiKey) return next()) makes the auth middleware developer-friendly without sacrificing production security — just set API_KEY in your deployment environment

How Orbis AppSec Detected This

  • Source: Incoming HTTP requests to /api/config, /api/subscriptions, /api/sites, and /api/refresh — no identity information required
  • Sink: Route handler callbacks at index.js:37 and subsequent route registrations, which directly read from config, dataDir, and fs without any prior credential check
  • Missing control: No authentication middleware (no req.headers token check, no session validation, no passport strategy) was present on any of the four affected route registrations
  • CWE: CWE-306 — Missing Authentication for Critical Function
  • Fix: Added apiAuth middleware that validates process.env.API_KEY against the x-api-key request header, applied to each sensitive route handler as a second argument to app.get()

Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.


Conclusion

Unauthenticated API endpoints are one of the most common and most preventable security issues in Node.js applications. The pattern is almost always the same: routes get added quickly during development, authentication is planned but deferred, and the application ships with open endpoints. In this case, four routes in index.js — handling config, subscriptions, sites, and refresh — were all reachable without a single credential check.

The fix is clean and instructive: a small apiAuth middleware function, a CSRF protection layer using crypto.randomBytes() for a secure secret, and path.basename() to neutralize path traversal in file reads. None of these changes are complex, but together they transform an open API into one that requires explicit authorization.

If you're building Express.js services, audit your route registrations today. A one-line grep can surface every unprotected endpoint in minutes — and Orbis AppSec can do it automatically on every pull request.


References

Frequently Asked Questions

What is missing authentication in Express.js APIs?

Missing authentication means HTTP route handlers process requests without verifying the caller's identity — no token, session, or credential check is performed before returning data or executing logic.

How do you prevent missing authentication in Node.js?

Apply authentication middleware (e.g., checking an `x-api-key` header against an environment variable, or validating a JWT) to every sensitive route, ideally by mounting it on the route prefix with `app.use('/api', authMiddleware)`.

What CWE is missing authentication?

CWE-306 — "Missing Authentication for Critical Function" — covers cases where software does not authenticate users before granting access to sensitive functionality.

Is CORS configuration enough to prevent unauthorized API access?

No. CORS is enforced by browsers but can be trivially bypassed by server-side tools like `curl` or custom HTTP clients. Proper authentication middleware is required.

Can static analysis detect missing authentication?

Yes. Tools like Semgrep, Snyk, and multi-agent AI scanners (as used here) can identify Express.js route handlers that lack authentication middleware by analyzing the route registration call signatures.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #8

Related Articles

critical

How Missing Authentication Middleware Happens in Node.js APIs and How to Fix It

A critical vulnerability in a Node.js Panel Connector API (CVE-2025-7783) left 14 endpoints—including shell command execution, file deletion, and file writing—completely open to unauthenticated access. The comment in the source code even declared "NO AUTH — Full Open Access," making it a textbook example of a missing authentication control. The fix adds a Bearer token middleware guard on all `/api` routes, blocking unauthorized requests before they reach any sensitive handler.

critical

How OAuth CSRF Attacks Happen in Node.js and How to Fix Them

A missing OAuth state parameter validation in `src/account_manager.js` left the `startOAuthServer()` function vulnerable to CSRF attacks, allowing an attacker to inject their own authorization code into a victim's active OAuth session. The fix generates a cryptographically random state token using `crypto.randomBytes()`, returns it alongside the server handle, and rejects any callback where the returned state doesn't match — closing the attack window entirely. This affects all downstream consume

critical

How Unauthenticated API Endpoint Exposure happens in Node.js and how to fix it

A critical vulnerability in `api/firebase-config.js` exposed all Firebase configuration values — including API keys, app IDs, and project IDs — to any unauthenticated caller. With no access controls, CORS restrictions, or rate limiting in place, attackers could retrieve live credentials and directly access Firebase services. The fix adds shared-secret authentication using timing-safe comparison, origin validation, and method enforcement.

high

How Middleware and Proxy Bypass happens in Next.js App Router and how to fix it

CVE-2026-64642 is a high-severity authentication bypass vulnerability in Next.js that affects App Router applications using Turbopack with a single locale configuration. The flaw allows attackers to circumvent middleware and proxy security controls, potentially gaining unauthorized access to protected routes. Upgrading from Next.js 16.2.7 to 16.2.11 closes the vulnerability entirely.

critical

How OAuth 2.0 CSRF happens in PHP and how to fix it

A critical OAuth 2.0 CSRF vulnerability in `login_weibo.php` allowed attackers to forge Weibo login requests by exploiting the missing `state` parameter validation. Without this check, an attacker could trick a victim's browser into completing an OAuth flow with the attacker's authorization code, potentially hijacking the victim's session. The fix generates a cryptographically random state token, stores it in the session, and validates it on callback.

critical

How Unauthenticated API Endpoints happen in Node.js Express and how to fix it

The `/token` endpoint in `plugin/multiplex/index.js` generated presentation control tokens without verifying the requester's identity, allowing any attacker with network access to seize control of a live reveal.js presentation. The fix restricts token generation to localhost-only requests and replaces a broken cryptographic primitive with a proper SHA-256 hash. Together, these changes eliminate both the access-control gap and a secondary cryptographic weakness in a single targeted patch.