A single unprotected API route or Server Action is a complete authorization bypass for any operation it performs. API routes in Next.js are public endpoints by default — authentication and authorization must be added explicitly. CWE-285 (Improper Authorization) and CWE-284 (Improper Access Control) are both in play when a route performs database writes without verifying who is asking. OWASP A01 (Broken Access Control) is the top web application risk category for this exact reason. NIST 800-53 AC-3 and AC-6 require access enforcement before any privileged operation executes. Server Actions receive the same treatment — they are callable from the browser and must not assume caller identity.
Critical because a single unprotected route exposes all of its data operations to unauthenticated callers, with no authentication barrier between the public internet and the database.
Every route handler that touches the database must begin with a session check. Treat every route as a public endpoint and require explicit proof of authorization before acting.
// app/api/posts/route.ts
import { auth } from '@/lib/auth';
export async function GET(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
// Now safe to query
const posts = await db.post.findMany({ where: { userId: session.user.id } });
return NextResponse.json(posts);
}
After fixing, audit completeness by grepping for database operations (.findMany, .create, .update, .delete) and confirming every one is preceded by a session check in its call chain.
ID: saas-authorization.resource-auth.every-api-checks-perms
Severity: critical
What to look for: Count all relevant instances and enumerate each. Examine every route handler in app/api/ (Next.js App Router route.ts files), pages/api/ (Pages Router), and Server Actions ("use server" files). For each handler that performs any database read or write, check whether the handler retrieves and validates the current user's session before acting. Pay special attention to: POST/PUT/PATCH/DELETE handlers, handlers with dynamic segments ([id], [slug]), and Server Actions that mutate data.
Pass criteria: Every route handler that accesses or mutates data begins with a session check. At least 1 implementation must be verified. The handler returns a 401 or 403 before touching the database if the session is missing or insufficient.
Fail criteria: Any route handler performs a database operation (query, insert, update, delete) without first verifying the current user's session and authorization. Even one unprotected route is a fail.
Skip (N/A) when: Project has no API routes and no Server Actions (pure static site with no backend functionality).
Detail on fail: "Found N API route(s) or Server Action(s) that perform data operations without an auth check. Affected files: [list]." (Always list the specific routes — this is critical.)
Remediation: Every route handler must be treated as a public endpoint by default, requiring explicit proof of authorization before acting. In Next.js App Router:
// app/api/posts/route.ts
import { auth } from '@/lib/auth';
export async function GET(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
// Now safe to query
const posts = await db.post.findMany({ where: { userId: session.user.id } });
return NextResponse.json(posts);
}
For Server Actions, the same applies — they are callable from the browser and must not assume the caller is authorized.
After fixing, verify by searching for database operations (.findMany, .findUnique, .create, .update, .delete) and confirming every one is preceded by a session check in the call chain.