typescript 77 lines · 3 tabs

Next.js middleware for auth gating

Shared by codesnips Jan 2026
3 tabs
import { NextRequestWithAuth } from 'next/server';
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { verifySession } from './lib/verify-session';
import { isProtected, isAuthPage } from './lib/route-matchers';

export async function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;
  const token = req.cookies.get('session')?.value;
  const session = token ? await verifySession(token) : null;

  if (isProtected(pathname) && !session) {
    const loginUrl = new URL('/login', req.url);
    loginUrl.searchParams.set('from', pathname + req.nextUrl.search);
    return NextResponse.redirect(loginUrl);
  }

  if (isAuthPage(pathname) && session) {
    return NextResponse.redirect(new URL('/dashboard', req.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/account/:path*', '/login', '/register'],
};
3 files · typescript Explain with highlit

This snippet shows how route protection is centralized in Next.js Middleware so that unauthenticated requests never reach protected pages. The core idea is that Middleware runs on the Edge runtime before rendering, which makes it the correct place to enforce access decisions without duplicating checks in every page or layout.

In middleware.ts, the config.matcher limits execution to the paths that actually need gating, avoiding overhead on static assets and public routes. The handler reads the session token from an HTTP-only cookie and calls verifySession to validate it. When a token is missing or invalid on a protected route, the code builds a redirect to /login and preserves the originally requested URL in a from query parameter so the user can be returned after signing in. A subtle but important detail is the inverse rule: if an already-authenticated user hits /login, they are redirected away to the dashboard, preventing a logged-in user from seeing the auth screen.

The verifySession helper uses jose rather than jsonwebtoken because the Edge runtime lacks Node's crypto module, so a Web Crypto-compatible library is required. jwtVerify checks both the signature and the expiry, and the function returns null on any failure so callers treat verification as a simple boolean-like gate. The secret is encoded once at module scope to avoid re-encoding on every request.

Matching decisions rely on isProtected and isAuthPage in route-matchers.ts, which keep the path logic declarative and testable in isolation. Using prefix matching here means nested routes like /dashboard/settings are covered automatically.

The trade-offs are worth noting. Middleware verification is stateless: it trusts the JWT signature but cannot know if a session was revoked server-side, so short token lifetimes or an additional server check for sensitive actions are advisable. Because Middleware runs at the edge, it must avoid Node-only APIs and heavy dependencies. The from redirect parameter should be validated before use to prevent open-redirect attacks. This pattern fits apps that want one authoritative access-control layer while keeping pages focused on rendering.


Related snips

Share this code

Here's the card — post it anywhere.

Next.js middleware for auth gating — share card
Link copied