Skip to content

Next.js Auth prompt

Rules that make your coding agent set up Better Auth in Next.js 16 the way it ships: one server instance, sessions checked next to the data, proxy.ts for redirects only, and plugins from the right package.

What it makes your agent good at

Asked for auth, agents still reach for NextAuth v4, put the only check in middleware.ts, protect data with a session read in a Client Component, or import passkeys from a path that no longer exists. This prompt gives them Better Auth 1.7 as installed: the instance and catch-all handler, session checks in the data layer and every Server Function, a cookie check in proxy.ts, and the CLI and plugins for organizations, two-factor and magic links.

  1. One server instance, one clientbetterAuth with a database adapter and nextCookies last, toNextJsHandler at /api/auth, createAuthClient with each plugin's client.
  2. Sessions checked where data livesauth.api.getSession with await headers(), cached per request and called in every Server Function and query, not only in a layout.
  3. proxy.ts as a fast redirectgetSessionCookie for an early redirect, no database reads, and never the only check between a visitor and your data.
  4. Plugins from the right packageorganization with access control, twoFactor, magicLink and admin from better-auth/plugins; passkeys from @better-auth/passkey.
  5. A schema that follows the confignpx auth generate after every plugin or field change, then the ORM's own migration; npx auth migrate only on Kysely.
  6. Production defaultsTrusted origins, shared rate-limit storage, real client IPs, versioned secrets, and the cookie cache skipped before sensitive writes.

The Next.js Auth rules

155 lines, current to Better Auth 1.7, Next.js 16.4 and React 19.3. Last reviewed October 23, 2026. Plain Markdown, no front matter: each agent's is below.

Download

nextjs-auth.md

# Next.js Auth

You are working in a Next.js 16 App Router project that authenticates with Better Auth 1.7 and TypeScript. Write code for the versions in `package.json`: Better Auth changes quickly, and much of what you remember (NextAuth, `middleware.ts`, the old CLI, passkeys in the core package) no longer applies.

## Before you write code

- Read `package.json` for `better-auth`, `next` and the database layer (Prisma, Drizzle or a `pg` pool). Check every import against `node_modules/better-auth/package.json` `exports` before you use it.
- Find the existing server instance (`lib/auth.ts` or `auth.ts`) and client (`lib/auth-client.ts`) and extend them. There is one of each per app.
- Option names and defaults live in `node_modules/@better-auth/core/dist/types/init-options.d.mts`; each plugin's options sit in `node_modules/better-auth/dist/plugins/<name>/`.
- For session reads under Cache Components, read `node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md`.

## The server instance

```ts
// lib/auth.ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { nextCookies } from "better-auth/next-js";
import { prisma } from "@/lib/db";

export const auth = betterAuth({
  database: prismaAdapter(prisma, { provider: "postgresql" }),
  emailAndPassword: { enabled: true, requireEmailVerification: true },
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID as string,
      clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
    },
  },
  plugins: [nextCookies()], // keep nextCookies last
});
```

- Adapters: `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma`, `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle`, or pass a `pg` `Pool` straight to `database` for the built-in Kysely adapter. With an ORM adapter, `betterAuth` from `better-auth/minimal` leaves Kysely out of the bundle.
- `nextCookies()` from `better-auth/next-js` lets `auth.api` calls in Server Functions set cookies. It must be the last plugin in the array.
- Secrets come from the environment: `BETTER_AUTH_SECRET` (32+ random bytes, `openssl rand -base64 32`; production throws without one) and `BETTER_AUTH_URL` (the app's origin). Rotate with `secrets: [{ version, value }]` or `BETTER_AUTH_SECRETS`, never by swapping the secret in place.
- Extend the user with `user.additionalFields` (`{ type, required, defaultValue, input: false }`). Set `input: false` on anything a user must not write at sign-up, such as a role or plan.
- Run side effects in `databaseHooks` (`user.create.after`, `session.create.before`) rather than wrapping the endpoints.
- `emailAndPassword` hashes with scrypt by default. Wire `sendResetPassword({ user, url, token })` (with `revokeSessionsOnPasswordReset: true`) and `emailVerification.sendVerificationEmail({ user, url, token })`. Send the email in the background (`after()` from `next/server`) rather than awaiting it, so response time does not reveal whether an account exists.

## Route handler and client

```ts
// app/api/auth/[...all]/route.ts
import { toNextJsHandler } from "better-auth/next-js";
import { auth } from "@/lib/auth";

export const { GET, POST } = toNextJsHandler(auth);
```

```ts
// lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
import { inferAdditionalFields } from "better-auth/client/plugins";
import type { auth } from "@/lib/auth";

export const authClient = createAuthClient({
  plugins: [inferAdditionalFields<typeof auth>()],
});
```

- The catch-all path must match `basePath` (default `/api/auth`). Social callbacks land on `/api/auth/callback/<provider>`: register exactly that URL with each provider.
- Client code takes the server's type with `import type`, never `lib/auth.ts` itself.
- Add each server plugin's client counterpart from `better-auth/client/plugins` (`organizationClient`, `twoFactorClient`, `magicLinkClient`, `adminClient`).
- Client calls return `{ data, error }` and do not throw: `authClient.signIn.email({ email, password, callbackURL })`, `authClient.signIn.social({ provider: "github", callbackURL })`, `authClient.signUp.email(...)`, `authClient.signOut()`. Check `error` and show `error.message`.
- `authClient.useSession()` returns `{ data, isPending, error, refetch }`. Use it for UI state in Client Components, not to protect anything.

## Reading the session on the server

```ts
// lib/dal.ts
import "server-only";
import { cache } from "react";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { auth } from "@/lib/auth";

export const getSession = cache(async () =>
  auth.api.getSession({ headers: await headers() })
);

export async function requireUser() {
  const session = await getSession();
  if (!session) redirect("/sign-in");
  return session.user;
}
```

- `auth.api.getSession({ headers: await headers() })` returns `{ session, user }` or `null`. Wrap it in React `cache()` so a layout and a page share one read.
- Authorize in the data layer and in every Server Function and Route Handler: call `requireUser()` (or check the role or organization) next to the query or mutation. A check in a layout does not protect the page or the action beneath it.
- Server Functions sign in with `auth.api.signInEmail({ body: { email, password }, headers: await headers() })`; `nextCookies()` writes the cookie. Errors throw `APIError` (from `better-auth/api`): catch it and return the message as a value, and call `redirect()` after the `try`.
- Pass the client only what it needs (`{ id, name, image }`), never the session token or the whole row.
- Use `auth.$Infer.Session` for the session type instead of hand-writing one.
- With `cacheComponents`, the session is request data: render what reads it inside `<Suspense>`, never call `getSession` inside a plain `"use cache"` function, and pass `user.id` into cached functions as an argument.
- With `session.cookieCache` enabled, a session can outlive its revocation for `maxAge`. Before a sensitive change (email, password, billing, deleting data), read with `query: { disableCookieCache: true }`.

## proxy.ts: an optimistic check only

```ts
// proxy.ts
import { type NextRequest, NextResponse } from "next/server";
import { getSessionCookie } from "better-auth/cookies";

export function proxy(request: NextRequest) {
  if (!getSessionCookie(request)) {
    return NextResponse.redirect(new URL("/sign-in", request.url));
  }
  return NextResponse.next();
}

export const config = { matcher: ["/dashboard/:path*", "/settings/:path*"] };
```

- `getSessionCookie` only sees whether a cookie exists; it does not validate it. Use it to redirect signed-out visitors early, then validate in the data layer as above.
- No database reads in `proxy.ts`: it runs on every matched request, prefetches included. Next.js 16 names the file `proxy.ts` and the function `proxy`.

## Schema and migrations

- Plugins add tables and columns. After adding or changing a plugin or `additionalFields`, regenerate: `npx auth generate` writes the Prisma or Drizzle schema, then apply it with that ORM's migrations (`prisma migrate dev`, `drizzle-kit`).
- `npx auth migrate` applies changes directly and only works with the built-in Kysely adapter.
- Change auth models through the config and regenerate, not by hand.

## Plugins

Import server plugins from `better-auth/plugins` and add each one's client plugin.

- `organization()` for B2B: organizations, members, invitations (`sendInvitationEmail`), an active organization on the session (`session.activeOrganizationId`), optional `teams`. Define roles with `createAccessControl` from `better-auth/plugins/access`, start from `defaultStatements`, `ownerAc`, `adminAc` and `memberAc` (`better-auth/plugins/organization/access`), and pass the same `ac` and `roles` to `organizationClient`. On the server, check with `auth.api.hasPermission({ headers, body: { permissions: { project: ["create"] } } })`; never trust a role sent by the client.
- `twoFactor({ issuer })` adds TOTP, email or SMS OTP and backup codes. On the client, `twoFactorClient({ onTwoFactorRedirect })` sends the user to your verification page when sign-in needs a second factor.
- Passkeys are not in `better-auth` 1.7: they ship as `@better-auth/passkey` with their own client plugin. Read its README in `node_modules` after installing; do not import a passkey plugin from `better-auth/plugins`.
- `magicLink({ sendMagicLink({ email, url }) })`, signed in with `authClient.signIn.magicLink({ email, callbackURL })`. Rate limit the send per address.
- `admin()` adds `role`, banning and impersonation (`adminRoles`, `impersonationSessionDuration`). Only an admin session may call its endpoints; check that server side too.

## Accounts, origins and rate limits

- Account linking is on by default: a social sign-in joins an existing user with the same email when the provider verified it (or is in `trustedProviders`) and the local user is verified. List only providers that verify emails in `trustedProviders`, set `disableImplicitLinking: true` to require explicit linking, and never enable `allowDifferentEmails` without a reason in writing.
- Let signed-in users connect providers with `authClient.linkSocial({ provider, callbackURL })` and handle the error when that account belongs to another user.
- `trustedOrigins` lists every origin allowed to call auth or appear in `callbackURL` (your production domain, preview and local origins; wildcards like `https://*.example.com` work). The base URL is trusted already. Never trust `*`.
- Rate limiting is on in production only and kept in memory by default, with stricter built-in rules for sign-in, sign-up and `/request-password-reset`. On serverless or several instances, set `rateLimit.storage` to `"database"` or `"secondary-storage"` so limits hold everywhere; adjust paths with `customRules` (`"/sign-in/email": { window: 60, max: 5 }`).
- Behind a proxy, set `advanced.ipAddress.ipAddressHeaders` to the header your host sets, so limits key on the real client IP.

## Do not

- Do not use NextAuth or Auth.js patterns (`getServerSession`, `[...nextauth]`, `useSession` from `next-auth/react`) in a Better Auth project.
- Do not write `middleware.ts`, query the database in `proxy.ts`, or treat the proxy cookie check as authorization.
- Do not read the session in a Client Component to protect data, store tokens in `localStorage`, or return the session token to the browser.
- Do not put `nextCookies()` anywhere but last, mount the handler at a path other than `basePath`, or create a second `betterAuth` instance per request.
- Do not run `npx auth migrate` against a Prisma or Drizzle project, or skip regenerating the schema after adding a plugin.
- Do not copy old guides' `@better-auth/cli` commands or import passkeys from `better-auth/plugins`.

## Before you finish

- Run the type checker and linter. Client methods for each plugin should type-check; if they do not, a client plugin is missing.
- Regenerate the schema and create a migration if you touched plugins or fields; read the migration before applying it.
- Test sign up, sign in, sign out and a protected Server Function while signed out (it must refuse), and confirm a social callback URL matches the provider's settings.
- List the environment variables you added (`BETTER_AUTH_SECRET`, `BETTER_AUTH_URL`, provider IDs and secrets) for the reviewer.

11,485 characters, 1,567 words. Under Windsurf's 12,000-character limit for a rule, as every prompt here is.

Use it in your agent

The same rules, where your agent reads them. Each file has the front matter its agent needs, set to load when the task fits.

Claude Code

A rule it loads every session

  1. Save it as .claude/rules/nextjs-auth.md in your project, front matter included, or run the install command.
  2. Its paths front matter loads it when Claude reads or edits a matching file, and keeps it out of other work.
  3. Prefer one file? Paste it into CLAUDE.md under its own heading instead.

.claude/rules/nextjs-auth.md

---paths:  - "**/auth.ts"  - "**/auth-client.ts"  - "app/api/auth/**"  - "proxy.ts"---# Next.js AuthYou are working in a Next.js 16 App Router project that authenticates with Better Auth 1.7 and TypeScript. Write code for the versions in `package.json`: Better Auth changes quickly, and much of what you remember (NextAuth, `middleware.ts`, the old CLI, passkeys in the core package) no longer applies.## Before you write code- Read `package.json` for `better-auth`, `next` and the database layer (Prisma, Drizzle or a `pg` pool). Check every import against `node_modules/better-auth/package.json` `exports` before you use it.- Find the existing server instance (`lib/auth.ts` or `auth.ts`) and client (`lib/auth-client.ts`) and extend them. There is one of each per app.- Option names and defaults live in `node_modules/@better-auth/core/dist/types/init-options.d.mts`; each plugin's options sit in `node_modules/better-auth/dist/plugins/<name>/`.- For session reads under Cache Components, read `node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md`.## The server instance…

Or let the shadcn CLI write this file: sign in (free) for your command.

Cursor

A project rule with front matter

  1. Save it as .cursor/rules/nextjs-auth.mdc, front matter included, or run the install command.
  2. Cursor attaches it when you work on matching files, or when the task fits its description.
  3. Check it under Cursor Settings, Rules, then commit the folder for your team.

.cursor/rules/nextjs-auth.mdc

---description: "Authentication with Better Auth in Next.js: the auth instance, route handler, client, session checks, proxy.ts, plugins and the auth schema."globs: **/auth.ts,**/auth-client.ts,app/api/auth/**,proxy.tsalwaysApply: false---# Next.js AuthYou are working in a Next.js 16 App Router project that authenticates with Better Auth 1.7 and TypeScript. Write code for the versions in `package.json`: Better Auth changes quickly, and much of what you remember (NextAuth, `middleware.ts`, the old CLI, passkeys in the core package) no longer applies.## Before you write code- Read `package.json` for `better-auth`, `next` and the database layer (Prisma, Drizzle or a `pg` pool). Check every import against `node_modules/better-auth/package.json` `exports` before you use it.- Find the existing server instance (`lib/auth.ts` or `auth.ts`) and client (`lib/auth-client.ts`) and extend them. There is one of each per app.- Option names and defaults live in `node_modules/@better-auth/core/dist/types/init-options.d.mts`; each plugin's options sit in `node_modules/better-auth/dist/plugins/<name>/`.- For session reads under Cache Components, read `node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md`.## The server instance…

Or let the shadcn CLI write this file: sign in (free) for your command.

Windsurf

A workspace rule for Cascade

  1. Save it as .windsurf/rules/nextjs-auth.md, or run the install command.
  2. trigger: model_decision lets Cascade pull it in when the task fits its description.
  3. Windsurf reads up to 12,000 characters per rule; this one is 11,485.

.windsurf/rules/nextjs-auth.md

---trigger: model_decisiondescription: "Authentication with Better Auth in Next.js: the auth instance, route handler, client, session checks, proxy.ts, plugins and the auth schema."---# Next.js AuthYou are working in a Next.js 16 App Router project that authenticates with Better Auth 1.7 and TypeScript. Write code for the versions in `package.json`: Better Auth changes quickly, and much of what you remember (NextAuth, `middleware.ts`, the old CLI, passkeys in the core package) no longer applies.## Before you write code- Read `package.json` for `better-auth`, `next` and the database layer (Prisma, Drizzle or a `pg` pool). Check every import against `node_modules/better-auth/package.json` `exports` before you use it.- Find the existing server instance (`lib/auth.ts` or `auth.ts`) and client (`lib/auth-client.ts`) and extend them. There is one of each per app.- Option names and defaults live in `node_modules/@better-auth/core/dist/types/init-options.d.mts`; each plugin's options sit in `node_modules/better-auth/dist/plugins/<name>/`.- For session reads under Cache Components, read `node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md`.## The server instance…

Or let the shadcn CLI write this file: sign in (free) for your command.

GitHub Copilot

Instructions for the repository

  1. Save it as .github/instructions/nextjs-auth.instructions.md, or run the install command.
  2. applyTo limits it to the files it's about, in chat, the coding agent and code review.
  3. Or paste it into .github/copilot-instructions.md, which Copilot reads for every request.

.github/instructions/nextjs-auth.instructions.md

---description: "Authentication with Better Auth in Next.js: the auth instance, route handler, client, session checks, proxy.ts, plugins and the auth schema."applyTo: "**/auth.ts,**/auth-client.ts,app/api/auth/**,proxy.ts"---# Next.js AuthYou are working in a Next.js 16 App Router project that authenticates with Better Auth 1.7 and TypeScript. Write code for the versions in `package.json`: Better Auth changes quickly, and much of what you remember (NextAuth, `middleware.ts`, the old CLI, passkeys in the core package) no longer applies.## Before you write code- Read `package.json` for `better-auth`, `next` and the database layer (Prisma, Drizzle or a `pg` pool). Check every import against `node_modules/better-auth/package.json` `exports` before you use it.- Find the existing server instance (`lib/auth.ts` or `auth.ts`) and client (`lib/auth-client.ts`) and extend them. There is one of each per app.- Option names and defaults live in `node_modules/@better-auth/core/dist/types/init-options.d.mts`; each plugin's options sit in `node_modules/better-auth/dist/plugins/<name>/`.- For session reads under Cache Components, read `node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md`.## The server instance…

Or let the shadcn CLI write this file: sign in (free) for your command.

Codex & AGENTS.md

The file most agents read

  1. Paste it into AGENTS.md at your project's root, under its own heading.
  2. Codex reads AGENTS.md before it starts work, and so do Cursor, Zed, Jules, Amp and Copilot's coding agent.
  3. A folder can have its own AGENTS.md: the one closest to the file being edited wins.

AGENTS.md

## Next.js AuthYou are working in a Next.js 16 App Router project that authenticates with Better Auth 1.7 and TypeScript. Write code for the versions in `package.json`: Better Auth changes quickly, and much of what you remember (NextAuth, `middleware.ts`, the old CLI, passkeys in the core package) no longer applies.### Before you write code- Read `package.json` for `better-auth`, `next` and the database layer (Prisma, Drizzle or a `pg` pool). Check every import against `node_modules/better-auth/package.json` `exports` before you use it.- Find the existing server instance (`lib/auth.ts` or `auth.ts`) and client (`lib/auth-client.ts`) and extend them. There is one of each per app.- Option names and defaults live in `node_modules/@better-auth/core/dist/types/init-options.d.mts`; each plugin's options sit in `node_modules/better-auth/dist/plugins/<name>/`.- For session reads under Cache Components, read `node_modules/next/dist/docs/01-app/02-guides/authentication-with-cache-components.md`.### The server instance…

AGENTS.md is shared with your own instructions, so paste the rules in: the copy sets their headings a level down.

Rules teach it. The MCP equips it. With Pro, your agent installs every component, block and icon it needs, from a prompt.

Get Pro

Pricing

Give your agent the parts, with Pro

The prompts are free. Every Pro plan adds the MCP server, so your agent installs the components, blocks and icons it writes code around.

4.9/5 out of 5 · 150+ reviewsfrom 150+ founders and developers.

Billed once a year in USD. Cancel anytime.

Individual

For personal projects & open source

$32instead of $49/mo

$389 billed yearly

  • How many people can use the license.Seats: 1 seat
  • Client work, SaaS products and anything you charge for.Commercial use: Personal & open source only
  • Page sections in 56 categories, from heroes and pricing tables to dashboards and settings, on every framework and UI library, in your theme.Blocks: All 6,181 blocks
  • Complete Next.js sites for startups, SaaS, agencies and portfolios, every page designed, ready to ship.Templates: All 14 templates
  • Your AI agent searches, previews and installs any component, block, icon or DESIGN.md from a prompt: 22 tools in Claude Code, Cursor, VS Code and 35 more clients.MCP server: 38 AI clients
  • Buttons with motion and states of their own, from an arrow that slides in to a burst of confetti.Animated buttons: All 35 buttons
  • Typewriters, scrambles, counters and more, for headlines, labels and numbers.Text effects: All 97 effects
  • Animated backgrounds for heroes, sections and cards, from a soft aurora to a flock that flees the cursor. Most are pure CSS.Backgrounds: All 100 backgrounds
  • WebGPU shaders, from a slow aurora to a black hole, each drawing only while it's on screen.Shaders: All 100 shaders
  • One design and one theme on all four: the components, blocks and effects are built for each framework.Frameworks: React, Vue, Svelte, Solid
  • React on Base UI, Radix UI, React Aria or Ark UI: install the version for the library your project already uses. Vue, Svelte and Solid run on Ark UI.UI libraries: Base UI, Radix, Aria, Ark
  • From a button to a date picker, a color picker or a guided tour: accessible, typed in TypeScript, each with live demos.Components: All 174 components
  • Real-world patterns built from the components, in 53 galleries: forms, tables, menus, dialogs and more.Examples: All 1,103 examples
  • Area, bar, line, pie, radar and radial charts on TanStack Charts, on every framework. React also gets the Recharts originals.Charts: All 70 charts
  • Debounce, local storage, media queries and more: each hook is one typed file you own.React hooks: All 35 hooks
  • From 222 libraries, Lucide, Heroicons, Tabler and Material Symbols among them, as SVG or a React component in one command.Icons: 294k+ icons
  • Stripe, Linear, Apple and 497 more: a brand's colors, typography and components in one file your AI agent builds from.DESIGN.md files: All 500 brands
  • Restyle every component and block at once: 8 styles, 7 grays, 18 colors and 5 radii, shared as a shadcn preset code.Theme builder: 8 styles × 18 colors
  • Priority requests are answered first.Support: Standard

Team

Most popular

For freelancers, agencies & startups

$66instead of $99/mo

$789 billed yearly · ≈ $13/dev/mo

Everything in Individual, plus

  • How many people can use the license.Seats: Up to 5 seats
  • Client work, SaaS products and anything you charge for.Commercial use: Included
  • Priority requests are answered first.Support: Priority

Organization

For companies with larger teams

$132instead of $199/mo

$1,589 billed yearly

Everything in Team, plus

  • How many people can use the license.Seats: Unlimited seats
  • Secure checkout via Stripe
  • Code lives in your repo
  • Keep what you've shipped if you cancel
  • License terms

Next.js Auth prompt FAQ

What the Next.js Auth prompt covers and how to use it in your agent.

Does the Next.js Auth prompt use Better Auth or NextAuth?

Better Auth 1.7. The prompt sets up one betterAuth instance with a database adapter, the catch-all route handler from toNextJsHandler and a client from better-auth/react. It also lists NextAuth and Auth.js patterns under Do not, so the agent stops mixing the two libraries.

Which databases does the Next.js Auth prompt connect Better Auth to?

Prisma with prismaAdapter, Drizzle with drizzleAdapter, or a pg Pool passed straight to the database option for the built-in Kysely adapter. The agent checks which one package.json already uses and extends that setup instead of adding another.

How does the Next.js Auth prompt handle Better Auth schema changes?

After any plugin or field change, the agent runs npx auth generate to update the Prisma or Drizzle schema, then creates a migration with that ORM. npx auth migrate is only for the built-in Kysely adapter, and the prompt says so, because running it on a Prisma project is a common mistake.

4.9 out of 5 from 150+ developers

Rules teach it. The MCP equips it.

Every component, block and icon, installed by your AI agent on your stack. The prompts stay free.

Get Pro
  • Cancel anytime
  • Keep what you've shipped