require-route-authentication
This rule detects routes that expose a critical function — credentials, accounts, payments, configuration — with no authentication middleware and no principal read in the handler
Require authentication on routes that expose a critical function (account, credential, payment, configuration)
Severity: 🔴 High (ships as warn — see below)
CWE: CWE-306
Rule Details
CWE-306 is the case where the check was never written, not the case where it is wrong: an endpoint that changes a password, moves money or edits configuration, reachable by anyone who can open a socket to the port.
The rule reports a route when all of the following hold:
- It is a route registration on an Express app/router (
app.post,router.delete, …) with a string-literal path. - The path matches the critical-function vocabulary (
password,credential,account,payment,billing,config,role,user, …) and does not match the public-by-design vocabulary (login,signup,reset,webhook,health,oauth,callback, …). Fragments match on word boundaries with an optional plurals, not as raw substrings:/usersand/orders/:idmatch,/reorder-itemsand/border-crossingdo not. - No argument before the final handler reads as authentication middleware (
requireAuth,authenticate,passport.authenticate('jwt'),verifyToken,ensureLoggedIn, …). - The handler body never reads an authenticated principal —
req.user,req.auth,req.session,res.locals.user, … - The file has no router-wide guard (
app.use(requireAuth)), wherever it appears — the check is deferred to the end of the file so a guard mounted after the routes still counts.
Because step 2 is a naming heuristic, the rule ships as warn in the recommended config and never at enforcement severity (plugin scope-audit invariant I3). It is a review prompt: every finding is a route worth a second look, not a proven vulnerability.
Examples
❌ Incorrect
// Account management with no guard at all
app.post('/users', createUser);
// Credential change behind a body parser, which is not authentication
app.put('/account/password', jsonParser, changePassword);
// Money movement
router.post('/payments/transfer', (req, res) => transfer(req.body));
// Configuration surface
app.all('/internal/config', updateConfig);✅ Correct
// Authentication in the route's own chain
app.post('/account/password', requireAuth, changePassword);
router.delete('/users/:id', authenticate, removeUser);
app.put('/billing/card', passport.authenticate('jwt'), saveCard);
// Router-wide guard — order does not matter
router.post('/users', createUser);
router.use(requireAuth);
// The handler resolves the principal itself
app.get('/account/profile', (req, res) => res.json(req.user));
// Word-boundary matching — "order"/"user" do not collide on English
app.post('/reorder-items', reorder);
app.post('/border-crossing', cross);
// Public by design — never reported
app.post('/login', doLogin);
app.post('/password/reset', resetPassword);
app.post('/webhooks/stripe/payment', handleStripe);
app.get('/health', healthCheck);Options
| Option | Type | Default | Description |
|---|---|---|---|
criticalPaths | string[] | ['password', 'credential', 'account', 'payment', …] | Path fragments that mark a route as a critical function (replaces the default set) |
publicPaths | string[] | ['login', 'signup', 'reset', 'webhook', 'health', …] | Path fragments that are public by design and never reported |
authMiddleware | string[] | [] | Extra middleware names accepted as authentication |
{
"rules": {
"express-security/require-route-authentication": [
"warn",
{
"criticalPaths": ["account", "billing", "tenant"],
"authMiddleware": ["withTenantContext"]
}
]
}
}When Not To Use It
If authentication is enforced entirely outside the application — an API gateway or service mesh that rejects unauthenticated requests before Express sees them — the rule reports routes that are in fact protected. Prefer configuring publicPaths over disabling it, so the routes that gateway does not cover still get flagged.
Known False Negatives
The following patterns are not detected due to static analysis limitations:
Non-Literal Route Paths
Why: The path must be a string literal to be matched against the vocabularies.
// ❌ NOT DETECTED
app.post(routes.createUser, createUser);Critical Endpoints With Ordinary Names
Why: "Critical function" is inferred from the path. A route named /v2/op that rotates credentials reads like anything else.
// ❌ NOT DETECTED
app.post('/v2/op', rotateSigningKeys);Mitigation: Add the fragment to criticalPaths, or name endpoints after what they do.
Auth-Shaped Names That Do Not Authenticate
Why: Middleware is matched by name. A middleware called sessionLogger suppresses the report without authenticating anything.
// ❌ NOT DETECTED — `session` reads as auth
app.post('/users', sessionLogger, createUser);Mitigation: Name middleware after its effect; requireX for guards, logX for logging.
Further Reading
- CWE-306: Missing Authentication for Critical Function
- OWASP A07:2021 – Identification and Authentication Failures
no-client-controlled-authorization— the check exists but trusts the caller (CWE-863)no-idor-resource-access— authenticated, but not scoped to the caller (CWE-639)
Did this rule catch something? Star the repo to get new CWE coverage as we ship it — or follow the AI-code-security benchmarks behind these rules.