API routes require authentication
Why it matters
An unauthenticated API route that reads or writes user data is a direct broken-access-control vulnerability — any visitor with the URL can enumerate profiles, export other tenants' records, or mutate state belonging to accounts they don't own. Optus (Australia) lost 10 million customer records through an unauthenticated API endpoint in 2022, triggering an AU$50M+ class action and regulatory action from the Australian government; T-Mobile disclosed a 37-million-customer breach in 2023 via a similar API-side failure. AI coding tools routinely produce this failure mode because they scaffold route handlers from example snippets that skip the session check, and because request-body validation visually looks like "protection" even though it enforces shape, not identity. The same pattern also catches webhook endpoints that accept arbitrary POST payloads without verifying the provider signature, which lets anyone forge subscription upgrades, payment confirmations, or delivery events. This is the single most common critical finding in post-incident reviews of vibe-coded apps.
Severity rationale
Critical because an unauthenticated data route is directly exploitable by any visitor with the URL — no credentials, tooling, or chained vulnerabilities required — and typically exposes multi-tenant data in bulk (Optus, T-Mobile, and dozens of smaller breaches have originated exactly here).
Remediation
Add an auth check at the top of each protected handler:
const session = await getSession()
if (!session) return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
Deeper remediation guidance and cross-reference coverage for this check lives in the data-protection Pro audit — run that after applying this fix for a more exhaustive pass on the same topic.
Detection
- ID:
api-routes-require-auth - Severity:
critical - What to look for: Enumerate API route files:
app/api/**/route.ts,pages/api/**, Express/Fastify registrations, tRPC mutations. Classify by HTTP method. Write handlers (POST/PUT/PATCH/DELETE): first 10 executable lines must call a session getter —auth(),getSession(),getServerSession(),supabase.auth.getUser(),supabase.auth.getSession(),requireUser(),currentUser(),clerkClient.users.getUser(),getToken(),verifyJWT(), or a local wrapper. Read handlers (GET): either the same session getter OR a// PUBLIC//* PUBLIC */marker directly above the handler. Webhook routes (/api/webhooks/*,/api/stripe/webhook, files matching*webhook*): first 10 lines must call a signature-verification function (stripe.webhooks.constructEvent,svix.verify,crypto.timingSafeEqualHMAC comparison) — webhooks are exempt from the session requirement when they verify a signature. - Pass criteria: 100% writes gated in first 10 lines; 100% GETs either gated or
// PUBLIC-marked; 100% webhook routes verify signatures in first 10 lines. - Fail criteria: Any write without a session getter in the first 10 lines. Any unmarked GET. Any webhook acting on
req.json()/request.bodybefore signature verification. Writes that readreq.body/req.formData()/await req.json()BEFORE the session getter — auth must come first, body parsing is attack surface. Internal-header shared-secret gates do NOT count as auth. A "middleware protects everything" claim does NOT count unless you trace the middleware file, confirm it calls a session getter, and confirm it 401/403s on miss. - Skip (N/A) when: No API routes. Quote:
"No handlers under app/api/**/route.ts, pages/api/**, or Express/Fastify registrations". - Report even on pass:
"N routes: W writes (all gated in first 10), R GETs (G gated, P // PUBLIC, 0 unmarked), X webhooks (all signature-verify). Zero unprotected." - Detail on fail:
"3 writes unprotected: app/api/users/[id]/route.ts PATCH reads req.json() at :4 with no session getter; app/api/orders/route.ts POST reads formData at :2 with no session check". - Cross-reference: For IDOR, over-fetching, tenant isolation, backup security, run
data-protection. - Remediation:
const session = await getSession() if (!session) return NextResponse.json({ error: 'unauthorized' }, { status: 401 })
Taxons
History
- 2026-04-22·v1.0.0·Initial import from project-snapshot via Phase 8.1 bundling·by phase-8-1-bundle-project-snapshot
- 2026-04-22·v2.0.0·Phase 9 consequence-first restructure — moved to new section slug, added news/incident references, severity reviewed.·by phase-9-stack-scan-v3
- 2026-04-23·v2.1.0·Phase 9.1 tighten — 100% coverage on write handlers, session check required in first 10 lines, body reads must come after auth, `// PUBLIC` marker required for unauthenticated GETs.·by phase-9-1-stack-scan-v3-1
- 2026-04-25·v2.1.1·v3.1.0 pre-ship trim — prose compression for under-80K MCP cap; merged overlapping Fail-criteria / Do-NOT-pass-when sections; compressed enumeration prose; one remediation example per pattern. No semantic change; anti-sycophancy guards preserved.·by phase-9-1-stack-scan-v3-1