# Authentication required on non-public endpoints

- **Pattern:** `ab-002195` (`saas-api-design.api-security.auth-required-non-public`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002195
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002195)

## Why it matters

Unprotected API endpoints that serve user-specific data or perform mutations are the canonical realization of OWASP A01:2021 (broken access control) and CWE-306 (missing authentication for critical function). The specific AI-generated antipattern — reading a `userId` from the request body rather than the verified session — maps to CWE-639 (authorization bypass through user-controlled key), enabling any authenticated user to read or mutate any other user's data by supplying a different ID. Frontend auth checks are irrelevant: API routes are directly callable via curl.

## Severity rationale

Critical because any unprotected endpoint exposing user data or mutations is directly exploitable without any authentication, enabling data theft or state corruption at zero friction.

## Remediation

Gate all non-public routes in `src/middleware.ts` with a route matcher, then derive the user identity exclusively from the verified session inside handlers — never from request body:

```ts
// app/api/users/me/route.ts
export async function GET(req: Request) {
  const session = await getServerSession(authOptions)
  if (!session) return new Response(null, { status: 401 })

  // Use session.user.id — never req.body.userId or searchParams.get('userId')
  const user = await db.user.findUnique({ where: { id: session.user.id } })
  return apiSuccess(serializeUser(user))
}
```

Any handler that currently accepts a `userId` in the request body and queries with it is a horizontal privilege escalation vulnerability — replace those immediately with session-derived IDs.

## Detection

- **ID:** `auth-required-non-public`
- **Severity:** `critical`
- **What to look for:** Enumerate all API route handlers. For each route, classify whether it should require authentication (routes that access user-specific data, mutate state, charge money, or expose private information). Then check whether authentication is actually enforced. Look for: session/token checks at the top of the handler, middleware that gates all routes (Next.js middleware matchers, Express middleware), auth library checks (e.g., `getServerSession()`, `auth()`, `verifyToken()`). Also look for: routes that check auth but only return different data rather than returning 401/403 — these are often a design mistake where auth is present but not enforced.
- **Pass criteria:** At least 100% of routes that handle private data or mutations check for a valid authentication token or session before proceeding. Unauthenticated requests receive 401. Unauthorized requests (authenticated but wrong role/ownership) receive 403. Public endpoints are explicitly designed as public.
- **Fail criteria:** Any route that accesses user-specific data or performs mutations without verifying authentication. Do not pass when auth is checked in the frontend but not enforced server-side. Routes that read a user ID from the request body instead of from the verified session token (this is a common AI-generated pattern that allows horizontal privilege escalation).
- **Skip (N/A) when:** The project has only fully public endpoints with no user-specific data (e.g., a public content API, a public pricing API). Signal: no auth library detected AND no user-specific data in any route response.
- **Detail on fail:** Example: `GET /api/user/profile accessible without auth`. Name the specific unprotected routes (e.g., "GET /api/user/profile and GET /api/billing/invoices accessible without authentication; /api/admin routes have no auth check"). Max 500 chars.
- **Remediation:** Apply authentication checks in `src/middleware.ts` to all non-public routes. The safest pattern is to use middleware that runs before any route handler — in Next.js, use `middleware.ts` with a route matcher that gates your `/api/` paths. Within handlers, always derive the user identity from the verified session/token (e.g., `const { userId } = await getServerSession()`) rather than trusting a user-supplied ID in the request body. If a handler receives a `userId` from the request body and uses it to look up data, replace that with the session-derived ID.
- **Cross-reference:** The SaaS Auth & Sessions Audit covers session management, token rotation, and privilege escalation patterns in detail.

## External references

- cwe CWE-306
- cwe CWE-639
- owasp A01
- owasp A07

Taxons: access-control

HTML version: https://auditbuffet.com/patterns/ab-002195
