A guard who skips some corridors
Watch it happen
The guard only watches the dashboard corridor. Mallory, with no badge, is stopped there but walks down the unwatched API corridor to the customer data.
The bank has a guard who checks badges, but he only has one corridor on his list: /dashboard. Mallory walks in with no badge.
In bank terms and app terms
| In the bank | In your app |
|---|---|
Are you exposed?
Your middleware matcher lists /dashboard and similar pages, but not /api/:path*. Your middleware only checks that a cookie exists instead of verifying the session or token. In Express, some app.get or router routes appear above the line where you add app.use(authMiddleware).
Make the matcher cover every path (pages and /api) and list the public exceptions explicitly. Verify the token or session in the middleware; do not accept “a cookie is present” as proof. In Express, register auth middleware before the routes it protects (public ones like login go separately). Never rely on middleware alone: check auth in sensitive handlers too, and keep Next.js patched (CVE-2025-29927, fixed in 12.3.5, 13.5.9, 14.2.25 and 15.2.3, let a crafted header skip middleware).
What it looks like in code
// GOOD: Protect both pages and API routes
import { jwtVerify } from "jose"
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"]
}
// Exact paths, not prefixes: "/api/health-anything" must not skip auth
const PUBLIC_PATHS = new Set(["/api/auth/login", "/api/auth/register", "/api/health"])
export async function middleware(request: NextRequest) {
if (PUBLIC_PATHS.has(request.nextUrl.pathname)) {
return NextResponse.next()
}
// Verify the token; don't just check that a header exists
const token = request.headers.get("Authorization")?.replace(/^Bearer /, "")
try {
if (!token) throw new Error("missing token")
await jwtVerify(token, SECRET, { algorithms: ["HS256"] })
} catch {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 })
}
return NextResponse.next()
}
Ask your AI tool to fix it
Paste this into Cursor, Claude, Lovable, Bolt or whatever you build with.
Review my authentication middleware. For Next.js, make the matcher cover /api/:path* as well as pages, with an explicit allow-list of public paths, and verify the session or token instead of checking only that a cookie exists. For Express, move auth middleware above every route it should protect with app.use("/api", authMiddleware). Keep public routes such as login and health checks outside it, and also check the session inside sensitive handlers. List every route that was previously unprotected.Where this comes from
The facts, figures and fixes on this page were checked against these sources on 25 Oct 2026. They are linked so you can check for yourself.
- Authorization Bypass in Next.js MiddlewareGitHub Advisory DatabaseCVE-2025-29927; fixed in 12.3.5, 13.5.9, 14.2.25, 15.2.3
- Postmortem on Next.js Middleware bypassVercelx-middleware-subrequest header skipped middleware; don't rely on middleware alone
- proxy.jsNext.jsMatcher scope; middleware renamed proxy in v16; verify auth in each Server Function
- How to think about data security in Next.jsNext.jsRe-check auth inside each action; page or proxy checks are not enough
- Using middlewareExpressMiddleware runs as a chain; mount paths with app.use; earlier routes can end the request
- A01:2025 Broken Access ControlOWASPForce browsing to unprotected URLs; deny by default
Spot a mistake or something out of date? Report a correction · How these guides are made and checked
The free, open-source scanner checks for most of the problems in these guides.