Stop 35 of 42 · The Rulebook · Middleware coverage gaps

A guard who skips some corridors

The bank posts a guard at the lobby with a list of corridors to watch. The list says “dashboard.” But the building also has an “api” corridor, and nobody wrote it down. The guard faithfully checks one corridor while the other stands wide open.

Severity Low–HighCategory Auth WeaknessThe RulebookSources checked 25 Oct 2026

Watch it happen

Play it as

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.

Arrival

The bank has a guard who checks badges, but he only has one corridor on his list: /dashboard. Mallory walks in with no badge.

Mallory says: Just looking around.
1 / 5

In bank terms and app terms

In the bankIn your app
Guard at the lobbyMiddleware that runs before routes (Next.js middleware.ts, renamed proxy.ts in Next.js 16, or Express app.use)
The guard’s list of corridorsThe matcher in config, or the paths you mount the middleware on
An unlisted corridorRoutes the matcher misses, like /api, or path variants its pattern does not cover
Guard arriving lateExpress routes defined before the auth middleware is registered

Are you exposed?

You might be exposed if…
  • 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).
What to do about it
  1. Make the matcher cover every path (pages and /api) and list the public exceptions explicitly.
  2. Verify the token or session in the middleware; do not accept “a cookie is present” as proof.
  3. 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).

Track these in your to-do list →

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.

Prompt
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.

Spot a mistake or something out of date? Report a correction · How these guides are made and checked