From 303a13ceaf33ae5cd85efffc748b0a82ae43ebcd Mon Sep 17 00:00:00 2001 From: emmaccen Date: Sat, 31 Jan 2026 23:00:56 +0100 Subject: [PATCH] docs: improve Next.js auth protection clarity and structure --- docs/content/docs/integrations/next.mdx | 359 +++++++++++------------- 1 file changed, 159 insertions(+), 200 deletions(-) diff --git a/docs/content/docs/integrations/next.mdx b/docs/content/docs/integrations/next.mdx index d0582b84a1..2232f5a1ad 100644 --- a/docs/content/docs/integrations/next.mdx +++ b/docs/content/docs/integrations/next.mdx @@ -127,131 +127,28 @@ const signIn = async () => { ## Auth Protection -In Next.js proxy/middleware, it's recommended to only check for the existence of a session cookie to handle redirection. To avoid blocking requests by making API or database calls. +### Recommended Approach -### Next.js 16+ (Proxy) +The recommended way to protect routes in Next.js is: -Next.js 16 replaces "middleware" with "proxy". You can use the Node.js runtime for full session validation with database checks: +1. **Middleware (Optional)**: Fast cookie-based check for better UX +2. **Page/Route Level (Required)**: Full session validation for security -```ts title="proxy.ts" -import { NextRequest, NextResponse } from "next/server"; -import { headers } from "next/headers"; -import { auth } from "@/lib/auth"; + +**Important**: Middleware runs on EVERY request and blocks all other requests while executing. Database calls in middleware slow down your entire application. For this reason, we recommend using lightweight cookie checks in middleware and performing full session validation at the page/route level. + -export async function proxy(request: NextRequest) { - const session = await auth.api.getSession({ - headers: await headers() - }) +### Cookie-Based Check in Middleware (Recommended) - // THIS IS NOT SECURE! - // This is the recommended approach to optimistically redirect users - // We recommend handling auth checks in each page/route - if(!session) { - return NextResponse.redirect(new URL("/sign-in", request.url)); - } - - return NextResponse.next(); -} - -export const config = { - matcher: ["/dashboard"], // Specify the routes the middleware applies to -}; -``` - -For cookie-only checks (faster but less secure), use `getSessionCookie`: - -```ts title="proxy.ts" +Use this approach for a fast, non-blocking redirect before protected pages load: +```ts title="middleware.ts" import { NextRequest, NextResponse } from "next/server"; import { getSessionCookie } from "better-auth/cookies"; -export async function proxy(request: NextRequest) { +export async function middleware(request: NextRequest) { const sessionCookie = getSessionCookie(request); - // THIS IS NOT SECURE! - // This is the recommended approach to optimistically redirect users - // We recommend handling auth checks in each page/route if (!sessionCookie) { - return NextResponse.redirect(new URL("/", request.url)); - } - - return NextResponse.next(); -} - -export const config = { - matcher: ["/dashboard"], // Specify the routes the middleware applies to -}; -``` - - -**Migration from middleware:** Rename `middleware.ts` → `proxy.ts` and `middleware` → `proxy` function. All Better Auth methods work identically. - - -### Next.js 15.2.0+ (Node.js Runtime Middleware) - -From Next.js 15.2.0, you can use the Node.js runtime in middleware for full session validation with database checks: - -```ts title="middleware.ts" -import { NextRequest, NextResponse } from "next/server"; -import { headers } from "next/headers"; -import { auth } from "@/lib/auth"; - -export async function middleware(request: NextRequest) { - const session = await auth.api.getSession({ - headers: await headers() - }) - - // THIS IS NOT SECURE! - // This is the recommended approach to optimistically redirect users - // We recommend handling auth checks in each page/route - if(!session) { - return NextResponse.redirect(new URL("/sign-in", request.url)); - } - - return NextResponse.next(); -} - -export const config = { - runtime: "nodejs", // Required for auth.api calls - matcher: ["/dashboard"], // Specify the routes the middleware applies to -}; -``` - - -Node.js runtime in middleware is experimental in Next.js versions before 16. Consider upgrading to Next.js 16+ for stable proxy support. - - -### Next.js 13-15.1.x (Edge Runtime Middleware) - -In older Next.js versions, middleware runs on the Edge Runtime and cannot make database calls. Use cookie-based checks for optimistic redirects: - - -The getSessionCookie() function does not automatically reference the auth config specified in auth.ts. Therefore, if you customized the cookie name or prefix, you need to ensure that the configuration in getSessionCookie() matches the config defined in your auth.ts. - - -#### For Next.js release `15.1.7` and below - -If you need the full session object, you'll have to fetch it from the `/api/auth/get-session` API route. Since Next.js middleware doesn't support running Node.js APIs directly, you must make an HTTP request. - - - The example uses [better-fetch](https://better-fetch.vercel.app), but you can use any fetch library. - - -```ts title="middleware.ts" -import { betterFetch } from "@better-fetch/fetch"; -import type { auth } from "@/lib/auth"; -import { NextRequest, NextResponse } from "next/server"; - -type Session = typeof auth.$Infer.Session; - -export async function middleware(request: NextRequest) { - const { data: session } = await betterFetch("/api/auth/get-session", { - baseURL: request.nextUrl.origin, - headers: { - cookie: request.headers.get("cookie") || "", // Forward the cookies from the request - }, - }); - - if (!session) { return NextResponse.redirect(new URL("/sign-in", request.url)); } @@ -259,76 +156,16 @@ export async function middleware(request: NextRequest) { } export const config = { - matcher: ["/dashboard"], // Apply middleware to specific routes -}; -``` - -#### For Next.js release `15.2.0` and above - -From Next.js 15.2.0, you can use the Node.js runtime in middleware for full session validation with database checks: - - - You may refer to the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) for more information about runtime configuration, and how to enable it. - Be careful when using the new runtime. It's an experimental feature and it may be subject to breaking changes. - - -```ts title="middleware.ts" -import { NextRequest, NextResponse } from "next/server"; -import { headers } from "next/headers"; -import { auth } from "@/lib/auth"; - -export async function middleware(request: NextRequest) { - const session = await auth.api.getSession({ - headers: await headers() - }) - - if(!session) { - return NextResponse.redirect(new URL("/sign-in", request.url)); - } - - return NextResponse.next(); -} - -export const config = { - runtime: "nodejs", - matcher: ["/dashboard"], // Apply middleware to specific routes -}; -``` - -#### Cookie-based checks (recommended for all versions) - -```ts title="middleware.ts" -import { NextRequest, NextResponse } from "next/server"; -import { getSessionCookie } from "better-auth/cookies"; - -export async function middleware(request: NextRequest) { - const sessionCookie = getSessionCookie(request); - - // THIS IS NOT SECURE! - // This is the recommended approach to optimistically redirect users - // We recommend handling auth checks in each page/route - if (!sessionCookie) { - return NextResponse.redirect(new URL("/", request.url)); - } - - return NextResponse.next(); -} - -export const config = { - matcher: ["/dashboard"], // Specify the routes the middleware applies to + matcher: ["/dashboard/:path*"], // Apply to protected routes }; ``` - **Security Warning:** The `getSessionCookie` function only checks for the - existence of a session cookie; it does **not** validate it. Relying solely - on this check for security is dangerous, as anyone can manually create a - cookie to bypass it. You must always validate the session on your server for - any protected actions or pages. +**Security Warning**: `getSessionCookie` only checks if a session cookie exists, it does **not** validate it. Anyone can manually create a cookie to bypass this check. Always validate the session at the page/route level (see below). -If you have a custom cookie name or prefix, you can pass it to the `getSessionCookie` function. +If you have a custom cookie name or prefix, pass it to `getSessionCookie`: ```ts const sessionCookie = getSessionCookie(request, { cookieName: "my_session_cookie", @@ -337,25 +174,9 @@ const sessionCookie = getSessionCookie(request, { ``` -Alternatively, you can use the `getCookieCache` helper to get the session object from the cookie cache. - -```ts title="middleware.ts" -import { getCookieCache } from "better-auth/cookies"; - -export async function middleware(request: NextRequest) { - const session = await getCookieCache(request); - if (!session) { - return NextResponse.redirect(new URL("/sign-in", request.url)); - } - return NextResponse.next(); -} -``` - -### How to handle auth checks in each page/route - -In this example, we are using the `auth.api.getSession` function within a server component to get the session object, -then we are checking if the session is valid. If it's not, we are redirecting the user to the sign-in page. +### Full Session Validation (Required for Security) +Always validate the session in your protected pages, server actions, and API routes: ```tsx title="app/dashboard/page.tsx" import { auth } from "@/lib/auth"; import { headers } from "next/headers"; @@ -364,16 +185,154 @@ import { redirect } from "next/navigation"; export default async function DashboardPage() { const session = await auth.api.getSession({ headers: await headers() - }) + }); - if(!session) { - redirect("/sign-in") + if (!session) { + redirect("/sign-in"); } - return

Welcome {session.user.name}

+ return

Welcome {session.user.name}

; } ``` +**Why both middleware and page checks?** + +- **Middleware**: Provides instant redirect before the page loads (better UX, no flash of protected content) +- **Page validation**: Verifies the session is legitimate and not expired (actual security) + +Think of middleware as a bouncer at the door and page validation as checking your ID inside. + +### Alternative: Cookie Cache Helper + +You can also use `getCookieCache` to get the session object from the cookie cache in middleware: +```ts title="middleware.ts" +import { getCookieCache } from "better-auth/cookies"; + +export async function middleware(request: NextRequest) { + const session = await getCookieCache(request); + + if (!session) { + return NextResponse.redirect(new URL("/sign-in", request.url)); + } + + return NextResponse.next(); +} +``` + + +Like `getSessionCookie`, this does not validate the session against your database. Always validate at the page/route level. + + +--- + +### Advanced: Full Session Validation in Middleware + + +**Performance Warning**: The following approaches make database calls in middleware, which blocks ALL requests to your application. Only use this if you have a specific need and understand the performance implications. + + +#### Next.js 16+ (Proxy) + +Next.js 16 replaces "middleware" with "proxy". You can use the Node.js runtime for full session validation: +```ts title="proxy.ts" +import { NextRequest, NextResponse } from "next/server"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; + +export async function proxy(request: NextRequest) { + const session = await auth.api.getSession({ + headers: await headers() + }); + + if (!session) { + return NextResponse.redirect(new URL("/sign-in", request.url)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/dashboard/:path*"], +}; +``` + + +**Migration from middleware:** Rename `middleware.ts` → `proxy.ts` and `middleware` → `proxy` function. All Better Auth methods work identically. + + +#### Next.js 15.2.0+ (Node.js Runtime Middleware) + +From Next.js 15.2.0, you can use the Node.js runtime in middleware: +```ts title="middleware.ts" +import { NextRequest, NextResponse } from "next/server"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth"; + +export async function middleware(request: NextRequest) { + const session = await auth.api.getSession({ + headers: await headers() + }); + + if (!session) { + return NextResponse.redirect(new URL("/sign-in", request.url)); + } + + return NextResponse.next(); +} + +export const config = { + runtime: "nodejs", // Required for auth.api calls + matcher: ["/dashboard/:path*"], +}; +``` + + +Node.js runtime in middleware is experimental in Next.js versions before 16. See the [Next.js documentation](https://nextjs.org/docs/app/building-your-application/routing/middleware#runtime) for more details. + + +#### Next.js 13-15.1.x (Edge Runtime with HTTP Request) + +In older Next.js versions, middleware runs on the Edge Runtime and cannot make direct database calls. You can fetch the session via HTTP: +```ts title="middleware.ts" +import { betterFetch } from "@better-fetch/fetch"; +import type { auth } from "@/lib/auth"; +import { NextRequest, NextResponse } from "next/server"; + +type Session = typeof auth.$Infer.Session; + +export async function middleware(request: NextRequest) { + const { data: session } = await betterFetch( + "/api/auth/get-session", + { + baseURL: request.nextUrl.origin, + headers: { + cookie: request.headers.get("cookie") || "", + }, + } + ); + + if (!session) { + return NextResponse.redirect(new URL("/sign-in", request.url)); + } + + return NextResponse.next(); +} + +export const config = { + matcher: ["/dashboard/:path*"], +}; +``` + + +This example uses [better-fetch](https://better-fetch.vercel.app), but you can use any fetch library. + + + +The `getSessionCookie()` function does not automatically reference the auth config specified in `auth.ts`. If you customized the cookie name or prefix, ensure the configuration in `getSessionCookie()` matches your `auth.ts` config. + + +--- + ## Next.js 16 Compatibility Better Auth is fully compatible with Next.js 16. The main change is that "middleware" is now called "proxy". See the [Auth Protection](#auth-protection) section above for Next.js 16+ proxy examples. @@ -389,4 +348,4 @@ Or manually: - Rename `middleware.ts` → `proxy.ts` - Change function name: `middleware` → `proxy` -All Better Auth methods work identically. See the [Next.js migration guide](https://nextjs.org/docs/app/api-reference/file-conventions/proxy#migration-to-proxy) for details. +All Better Auth methods work identically. See the [Next.js migration guide](https://nextjs.org/docs/app/api-reference/file-conventions/proxy#migration-to-proxy) for details. \ No newline at end of file