Protected route handlers call a session getter
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.
// 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 underapp/(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:
// 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 — Missing Authorization
- cwe · CWE-306 — Missing Authentication for Critical Function
- owasp:2021 · A01 — Broken Access Control
- owasp:2021 · A07 — Identification and Authentication Failures
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-slop-security-theater·automated