Unauthenticated access to private routes is OWASP A01 (Broken Access Control) — the top-ranked web application vulnerability. CWE-285 (Improper Authorization) and CWE-287 (Improper Authentication) both apply when any private page or API route is reachable without a valid session. In Next.js, a middleware file placed at an incorrect path is silently ignored by the framework — every route it was intended to protect is exposed, with no error and no indication anything is wrong.
Critical because exposed private routes allow unauthenticated access to user data, admin functions, or financial records with no credentials required.
Place middleware.ts at src/middleware.ts or the project root — not in subdirectories. Ensure the matcher covers every private prefix, and validate auth in individual API routes as defense in depth:
// src/middleware.ts (or root middleware.ts) — correct location
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*', '/api/:path*']
}
export async function middleware(request: NextRequest) {
const session = await getSession(request)
if (!session) return NextResponse.redirect(new URL('/login', request.url))
}
// Also validate in each API route:
export async function GET(request: Request) {
const session = await getSession(request)
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
Do not list private routes and assume middleware will catch all gaps — enumerate every API route that returns user-specific data and confirm each has an independent auth check.
ID: saas-authentication.auth-flow.auth-middleware-private-routes
Severity: critical
What to look for: Examine the routing structure to identify private routes (dashboard, settings, admin, API routes that return user data). Then examine how authentication is enforced on these routes. In Next.js: look at middleware.ts — does the matcher cover all private route prefixes? Check if individual protected pages/layouts also verify auth, or if they rely solely on middleware. Check for any private API routes that don't validate the session token. CRITICAL: For Next.js, verify the middleware file is located at src/middleware.ts or middleware.ts in the project root. Middleware-like code at any other path (e.g., proxy.ts, auth-middleware.ts) is NEVER executed by the Next.js framework — its presence does NOT satisfy this check. Count all instances found and enumerate each.
Pass criteria: Every private route is protected by at least one authentication check. Either (a) a middleware intercepts all private route prefixes and redirects unauthenticated users, or (b) every protected page and API route individually validates the session. API routes that return user data must validate auth server-side even if the middleware covers page routes. For Next.js, the middleware file must be at src/middleware.ts or middleware.ts in the project root to be executed by the framework. At least 1 implementation must be confirmed.
Fail criteria: Private routes exist that are reachable without authentication — either because the middleware matcher doesn't cover them, or individual API routes skip the auth check while relying on the assumption that the frontend won't call them unauthenticated. For Next.js, a middleware file at an incorrect path (not src/middleware.ts or root middleware.ts) must be treated as absent — the routes it was intended to protect are unprotected.
Skip (N/A) when: No private routes exist — the application is entirely public with no user-specific data or actions.
Detail on fail: "Middleware matcher covers /dashboard/* but not /api/user/* — API routes return user data without session validation" or "Settings page at /app/(app)/settings/page.tsx fetches user data without checking session — accessible without auth".
Remediation: "Security by obscurity" — assuming the frontend won't navigate to unauthorized pages — is not security. Every private route must be independently protected:
// middleware.ts — cover all private prefixes
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*', '/api/:path*']
}
export async function middleware(request: NextRequest) {
const session = await getSession(request)
if (!session) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
// Also validate in each API route (defense in depth):
export async function GET(request: Request) {
const session = await getSession(request)
if (!session) return Response.json({ error: 'Unauthorized' }, { status: 401 })
// ... return user data
}
Do not rely solely on middleware for API route protection — middleware can have matcher gaps, and defense in depth matters.