DEV Community

Somnath Khadanga
Somnath Khadanga

Posted on

Middleware is not your auth layer (a Next.js footgun I keep finding)

When I audit Next.js apps built quickly (often with AI tools), one pattern shows up more than any other: routes are protected in middleware.ts, and the API route handlers trust that.

// app/api/admin/users/route.ts
export async function GET(request: NextRequest) {
  // "middleware already checked"
  const users = await db.user.findMany()
  return NextResponse.json(users)
}
Enter fullscreen mode Exit fullscreen mode

Middleware can be skipped by a matcher that doesn't cover a new route, misconfigured during a refactor, or bypassed by calling the API directly. If the handler doesn't check, the data is open.

The fix is boring: every handler that touches user data verifies the session and role itself.

export async function GET(request: NextRequest) {
  const session = await getServerSession(authOptions)
  if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  if (session.user.role !== 'admin') return NextResponse.json({ error: 'Forbidden' }, { status: 403 })

  const users = await db.user.findMany()
  return NextResponse.json(users)
}
Enter fullscreen mode Exit fullscreen mode

Two related checks worth doing while you're there:

  • Hiding the admin dashboard in React when role !== 'admin' is UI gating, not access control. The API behind it still needs the server-side check.
  • Delete a user's session row and make a request with their old cookie. If it still works, revocation isn't wired up.

Rule of thumb: middleware is a convenience layer for redirects and early exits. The security boundary is the handler.

How do you handle this in your apps: a shared requireUser() helper, or checks inline in each route?

Top comments (0)