# A client-side login check does not protect a Next.js route

> Routes outside a Next.js Proxy matcher can render before auth runs. Keep route coverage testable, then enforce authorization again near the data.

Source: https://saascode.ai/inside/auth-bypass-missing-routes · Published: 2026-08-21 · Section: academy

---
Log out, paste a dashboard URL into the address bar, and watch the status code. If the server returns `200` and lets the client discover that the session is missing, the route was never protected at the server boundary.

That happened to six top-level route groups in one live build. They sat outside the configured Next.js matcher and used client-side layouts, so logged-out visitors received empty application shells. The underlying APIs still returned `401`, so no private rows were exposed. The page routes were nevertheless public, their structure was visible, and every visit paid to render a screen the visitor could not use.

## Why route lists drift

Many applications maintain two versions of “protected routes”:

- a list used by session or role logic;
- the `config.matcher` patterns that decide where Next.js Proxy runs.

Adding a route to only one list creates a half-guarded page. In Next.js 16, the file is called `proxy.ts`; older versions called the same convention Middleware. The matcher still decides which paths enter that boundary.

Prefer one broad matcher with an explicit public-route decision inside the proxy when that fits the application. If you use a protected-route allowlist, derive every consumer from one route inventory rather than copying paths into two arrays.

```ts
const protectedPrefixes = ['/dashboard', '/settings', '/workspace']

export function proxy(request: NextRequest) {
  const protectedRoute = protectedPrefixes.some(prefix =>
    request.nextUrl.pathname.startsWith(prefix)
  )

  if (protectedRoute && !readSessionCookie(request)) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}
```

## Proxy is the first check, not the last

Next.js recommends using Proxy for optimistic checks and keeping secure authorization close to the data source. Follow that split:

1. Proxy rejects obviously unauthenticated page requests before rendering.
2. Server components, route handlers, and the data-access layer verify the current session and permission.
3. Database policies restrict the rows the caller can reach.

A protected-looking shell is not a security boundary, and a redirect is not a substitute for API authorization.

## Turn the sitemap into a test table

For every non-public route, send an unauthenticated request and assert a redirect to login rather than `200`. In the original case, the useful measured contrast was six groups returning `200` instead of the expected temporary redirect.

Run the same inventory against API routes and assert `401` or `403` as appropriate. That test catches both failures: pages accidentally omitted from the matcher and data endpoints that trusted the page to keep callers out.
