# Protected route handlers call a session getter

- **Pattern:** `ab-000284` (`ai-slop-security-theater.unbound-auth.protected-routes-call-session-getter`)
- **Severity:** critical
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-000284
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-000284)

## Why it matters

A file inside `app/(authenticated)/` or `app/admin/` that never calls `getServerSession()`, `auth()`, or an equivalent session getter is unprotected — the directory name is a routing convention, not a security boundary. CWE-862 (Missing Authorization) and OWASP A01 (Broken Access Control) describe this exactly. An unauthenticated user who discovers the route URL gets full access. This pattern is extremely common in AI-generated code because the model groups files into protected directories without always wiring the session check inside each file.

## Severity rationale

Critical because a missing session check in a protected route grants unauthenticated access to admin data, user records, or privileged operations — a direct OWASP A01 broken access control.

## Remediation

Call the session getter at the top of every protected route handler and redirect or return 401 immediately if the result is null.

```ts
// app/admin/users/page.tsx
import { auth } from '@/lib/auth'
import { redirect } from 'next/navigation'

export default async function Page() {
  const session = await auth()
  if (!session?.user || session.user.role !== 'admin') {
    redirect('/login')
  }
  const users = await prisma.user.findMany()
  return <UserList users={users} />
}
```

For API routes, return a 401 instead of redirecting. Do not rely on directory grouping in the App Router as a security mechanism — it affects routing only, not authentication.

## Detection

- **ID:** `protected-routes-call-session-getter`
- **Severity:** `critical`
- **What to look for:** Count all protected route files. Walk all protected route files (API handlers under `app/api/(authenticated)/**`, `app/api/(protected)/**`, `app/api/admin/**`, `pages/api/admin/**`, OR pages under `app/(authenticated)/**`, `app/(protected)/**`, `app/(dashboard)/**`, `app/admin/**`). For each file, count whether the file imports AND calls at least 1 session-getter function: `getServerSession`, `auth()`, `getUser`, `currentUser`, `getSession`, `cookies()` followed by JWT verification, `clerkAuth()`, `getKindeServerSession`, `getSupabaseServerClient`. Report files where the route is implied to be protected by directory naming but no session getter is called.
- **Pass criteria:** 100% of protected route files import AND call a session-getter function. Report: "X protected route files inspected, Y call a session getter, 0 unprotected."
- **Fail criteria:** At least 1 file in a protected route directory has no session-getter call.
- **Do NOT pass when:** A file imports a session getter but never calls it — the import alone doesn't protect anything.
- **Skip (N/A) when:** Project has no protected route directories detected (no `(authenticated)`, `(protected)`, `admin/`, etc.).
- **Cross-reference:** For deeper authorization analysis, the SaaS Authorization audit (`saas-authorization`) covers RBAC and permission patterns in depth.
- **Detail on fail:** `"2 protected routes don't check the session: app/admin/users/page.tsx (no getServerSession/auth() call), app/api/admin/delete/route.ts DELETE handler (no session lookup)"`
- **Remediation:** A protected route that doesn't actually check the session is unprotected. Always call the session getter at the top of the handler:

  ```ts
  // Bad: directory implies protection, but no auth check
  // app/admin/users/page.tsx
  export default async function Page() {
    const users = await prisma.user.findMany()
    return <UserList users={users} />
  }

  // Good: explicit session check
  import { auth } from '@/lib/auth'
  import { redirect } from 'next/navigation'

  export default async function Page() {
    const session = await auth()
    if (!session?.user || session.user.role !== 'admin') {
      redirect('/login')
    }
    const users = await prisma.user.findMany()
    return <UserList users={users} />
  }
  ```

## External references

- cwe CWE-862
- cwe CWE-306
- owasp A01
- owasp A07

Taxons: access-control

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