From 65029480a918c60aa048a04ddf0edd7a82069f1f Mon Sep 17 00:00:00 2001 From: Toan Hoang Minh Date: Fri, 31 Jul 2026 12:38:12 +0700 Subject: [PATCH] feat: add middleware for page route protection middleware.ts gates page routes by default (redirect to /login when unauthenticated), except /login itself and Next.js internal asset paths - config.matcher excludes /api entirely, /_next/static, /_next/image, and favicon.ico. Default-deny: everything requires login unless explicitly excluded, including the home page, rather than an opt-in allowlist new routes could forget to join. Builds its own lightweight NextAuth instance from auth.config.ts (no Credentials provider) instead of importing auth from auth.ts, to stay Edge-Runtime compatible - see auth.config.ts. API routes are deliberately NOT gated here (config.matcher excludes /api). Auth.js guidance is to verify the session as close to the data fetching as possible rather than rely on middleware alone; API route auth is added directly in each route handler instead (next commit). This project pins Next.js 14.2.35, which predates the Next.js 16 middleware.ts -> proxy.ts rename, so this stays middleware.ts with a default export, not proxy.ts. --- src/middleware.ts | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/middleware.ts diff --git a/src/middleware.ts b/src/middleware.ts new file mode 100644 index 0000000..33acd19 --- /dev/null +++ b/src/middleware.ts @@ -0,0 +1,47 @@ +import NextAuth from "next-auth"; +import { NextResponse } from "next/server"; +import authConfig from "@/auth.config"; + +// Route-gating for page requests: "everything requires login by default, +// explicit allowlist for what doesn't" (safer than an opt-in allowlist that +// new routes can forget to add themselves to). +// +// This project pins Next.js 14.2.35, which predates Next.js 16's +// middleware.ts -> proxy.ts rename (see https://nextjs.org/docs/messages/middleware-to-proxy) +// so this stays middleware.ts with a default-exported `middleware` function, +// not proxy.ts. Auth.js v5's docs (https://authjs.dev/getting-started/session-management/protecting) +// confirm: older Next.js versions re-export `auth` as `middleware`; only +// Next.js 16+ uses `proxy`. +// +// Deliberately builds its own NextAuth instance from src/auth.config.ts +// (no Credentials provider) instead of importing `auth` from src/auth.ts — +// this file runs on the Edge Runtime, and src/auth.ts's Credentials +// provider pulls in bcryptjs (Node-only); see auth.config.ts's comment. +// This instance only needs to decode/validate the JWT session cookie, which +// is Edge-safe. +// +// config.matcher below excludes /api entirely, so this middleware never +// runs on API routes (including /api/auth/* itself, needed for the login +// flow) — API route authorization is handled separately, directly in each +// route handler (see src/app/api/requests/route.ts), per Auth.js's own +// guidance to verify the session as close to the data fetching as possible +// rather than relying on middleware alone. That also means /login doesn't +// need special-casing in the matcher, only in the redirect check below (to +// avoid an infinite redirect loop), and Next.js's internal asset paths +// (_next/static, _next/image, favicon.ico) never reach this middleware. +const { auth } = NextAuth(authConfig); + +export default auth((req) => { + const isLoginPage = req.nextUrl.pathname === "/login"; + + if (!req.auth && !isLoginPage) { + const loginUrl = new URL("/login", req.nextUrl.origin); + return NextResponse.redirect(loginUrl); + } + + return NextResponse.next(); +}); + +export const config = { + matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"], +};