Next.js middleware can be bypassed (CVE-2025-29927). Always enforce auth checks inside route handlers and Server Actions as defense-in-depth. [CWE-287 · A01:2021]
Why This Matters
complete authentication bypass allowing unauthorized access to all protected routes
Impact: CRITICAL (complete authentication bypass allowing unauthorized access to all protected routes)
CVE-2025-29927 (CVSS 9.1) proved that Next.js middleware can be bypassed entirely with a single HTTP header. An internal x-middleware-subrequest header — designed to prevent infinite recursion — was trusted from external clients. Any attacker who set this header skipped all middleware logic, including auth checks, redirects, and access control. This affected Next.js 11.1.4 through 15.2.2.
Even on patched versions, middleware is a convenience layer, not a security boundary. The defense-in-depth rule: every route handler and Server Action must independently verify authentication and authorization.
Incorrect (relying solely on middleware for auth):
// middleware.ts — the ONLY place auth is checkedimport { NextResponse } from 'next/server'import { verifyToken } from '@/lib/auth'export function middleware(request: NextRequest) { const token = request.cookies.get('session')?.value if (!token || !verifyToken(token)) { return NextResponse.redirect(new URL('/login', request.url)) } return NextResponse.next()}export const config = { matcher: ['/dashboard/:path*', '/api/admin/:path*'],}// ❌ Route handler assumes middleware already checked auth// app/api/admin/users/route.tsexport async function DELETE(request: NextRequest) { const { userId } = await request.json() await db.user.delete({ where: { id: userId } }) // No auth check! return NextResponse.json({ success: true })}
If self-hosting, strip the header at the reverse proxy:
# nginx — block the internal header from external clientsproxy_set_header x-middleware-subrequest "";
Detection hints:
# Find route handlers that don't contain auth checksgrep -rn "export async function GET\|export async function POST\|export async function PUT\|export async function DELETE" src/ --include="*.ts" --include="*.tsx" -l | \ xargs grep -L "auth\|session\|getServerSession"# Check if middleware is the only auth layergrep -rn "matcher.*dashboard\|matcher.*admin\|matcher.*api" middleware.ts