Safe Action Better Auth

next-safe-action/skills/skills/safe-action-better-auth

作者 next-safe-actiona2605bd2e84321245cba8f5718c144a6e4a5fa47無授權條款收錄於 2026年10月9日更新於 2026年10月9日

Use when adding authentication or authorization to safe actions with Better Auth -- covers betterAuth() middleware setup, typed session context (BetterAuthContext), custom authorize callbacks (AuthorizeFn), unauthorized() handling, nextCookies() configuration, and Next.js authInterrupts setup

AI 產生的概覽

為 next-safe-action 伺服器動作加入 Better Auth 工作階段驗證與授權中介軟體。

功能
此技能說明如何將 @next-safe-action/adapter-better-auth 轉接器接入 next-safe-action 用戶端,讓伺服器動作要求已驗證的工作階段。內容涵蓋安裝套件、建立帶有 nextCookies() 外掛的 Better Auth 實例、啟用 Next.js 的 authInterrupts 旗標,以及把 betterAuth(auth) 中介軟體掛到動作用戶端上。它也說明具型別的內容(ctx.auth.user 與 ctx.auth.session)、authorize 回呼、unauthorized() 處理以及常見的反模式。隨附的參考檔案介紹自訂 authorize 模式,例如角色檢查、重新導向與組織存取。
適用情境
適用於在使用 Better Auth 的 Next.js 專案中,為安全動作加入驗證或授權的情境。適合需要具型別工作階段內容、自訂 authorize 邏輯,或登入註冊等設定 Cookie 的驗證流程。
執行需求
需要一個使用 next-safe-action 與 Better Auth 的 Next.js 專案,以及 @next-safe-action/adapter-better-auth 和 better-auth 這兩個 npm 套件。Next.js 設定需啟用實驗性的 authInterrupts 旗標,Better Auth 實例應包含 nextCookies() 外掛。此技能不附帶指令碼,僅為說明文件。

next-safe-action Better Auth Adapter

Install

bash
npm install @next-safe-action/adapter-better-auth better-auth

Import

ts
import { betterAuth } from "@next-safe-action/adapter-better-auth";

Quick Start

1. Set up Better Auth

Create your Better Auth server instance. Add the nextCookies() plugin if your actions need to set cookies (e.g. signInEmail, signUpEmail):

ts
// src/lib/auth.tsimport { betterAuth } from "better-auth";import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({  // ...your config (database, plugins, etc.)  plugins: [    // ...other plugins    nextCookies(), // must be the last plugin in the array  ],});

2. Enable auth interrupts in Next.js

The default behavior uses unauthorized() from next/navigation, which requires this flag:

ts
// next.config.tsimport type { NextConfig } from "next";
const nextConfig: NextConfig = {  experimental: {    authInterrupts: true,  },};
export default nextConfig;

3. Create an authenticated action client

ts
// src/lib/safe-action.tsimport { createSafeActionClient } from "next-safe-action";import { betterAuth } from "@next-safe-action/adapter-better-auth";import { auth } from "./auth";
// Public action client (no auth required)export const actionClient = createSafeActionClient();
// Authenticated action clientexport const authClient = actionClient.use(betterAuth(auth));

4. Use it in your actions

ts
// src/app/actions.ts"use server";
import { z } from "zod";import { authClient } from "@/lib/safe-action";
export const updateProfile = authClient  .inputSchema(z.object({ name: z.string().min(1) }))  .action(async ({ parsedInput, ctx }) => {    // ctx.auth.user and ctx.auth.session are fully typed,    // including fields from Better Auth plugins    const userId = ctx.auth.user.id;
    await db.user.update({      where: { id: userId },      data: { name: parsedInput.name },    });
    return { success: true };  });

How It Works

betterAuth() creates a pre-validation middleware for the safe action client's .use() chain:

  1. Fetches the session by calling auth.api.getSession({ headers: await headers() }) using the request headers from next/headers
  2. Blocks unauthenticated requests by calling unauthorized() from next/navigation when no session exists
  3. Injects typed context by passing { auth: { user, session } } to next(), merging it into the action context

The context is namespaced under auth to avoid collisions with other middleware context properties.

Type Inference

The middleware infers the exact user and session types from your Better Auth instance, including any fields added by plugins. For example, if you use the organization plugin, ctx.auth.session will include activeOrganizationId. No manual type annotations are needed.

Entry Points

Entry pointExportsEnvironment
@next-safe-action/adapter-better-authbetterAuth, typesServer

Exported Types

TypeDescription
BetterAuthContext<O>The context shape added by the middleware: { auth: { user, session } }. Types are inferred from the Better Auth instance via Auth<O>["$Infer"]["Session"].
AuthorizeFn<O, NC, Ctx>The authorize callback signature. Receives { authData, ctx, next }.
BetterAuthOpts<O, NC, Ctx>The options object type for betterAuth(). Contains the optional authorize callback.

vs. Manual Auth Middleware

If you are using Better Auth, prefer betterAuth(auth) over writing manual auth middleware. The adapter handles session fetching, cookie integration, typing, and unauthorized rejection automatically.

ts
// Manual — don't do this if you have @next-safe-action/adapter-better-auth installedconst authClient = actionClient.use(async ({ next }) => {  const session = await auth.api.getSession({ headers: await headers() });  if (!session) {    throw new Error("Unauthorized");  }  return next({ ctx: { userId: session.user.id } });});
// With adapter — do this insteadconst authClient = actionClient.use(betterAuth(auth));// ctx.auth.user and ctx.auth.session are fully typed automatically

Supporting Docs

  • Custom authorize patterns (role checks, redirects, org access)

Anti-Patterns

ts
// BAD: Missing nextCookies() plugin — cookies won't be set in Server Actions// Session will silently be null when actions try to set cookiesimport { betterAuth } from "better-auth";
export const auth = betterAuth({  plugins: [/* no nextCookies() */],});
// GOOD: Add nextCookies() as the last pluginimport { betterAuth } from "better-auth";import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({  plugins: [    // ...other plugins    nextCookies(), // must be last  ],});
ts
// BAD: Missing authInterrupts flag — unauthorized() will throw a runtime error// next.config.tsconst nextConfig: NextConfig = {};
// GOOD: Enable authInterruptsconst nextConfig: NextConfig = {  experimental: {    authInterrupts: true,  },};
ts
// BAD: Re-fetching session inside authorize — it's already pre-fetched as authDataactionClient.use(  betterAuth(auth, {    authorize: async ({ next }) => {      const session = await auth.api.getSession({ headers: await headers() }); // Redundant!      if (!session || session.user.role !== "admin") {        unauthorized();      }      return next({ ctx: { auth: session } });    },  }));
// GOOD: Use the pre-fetched authData directlyactionClient.use(  betterAuth(auth, {    authorize: ({ authData, next }) => {      if (!authData || authData.user.role !== "admin") {        unauthorized();      }      return next({ ctx: { auth: authData } });    },  }));
ts
// BAD: Writing manual Better Auth middleware when the adapter is installedimport { auth } from "./auth";
const authClient = actionClient.use(async ({ next }) => {  const session = await auth.api.getSession({ headers: await headers() });  if (!session) throw new Error("Unauthorized");  return next({ ctx: { user: session.user } });});
// GOOD: Use the adapter — handles typing, cookies, and unauthorized() automaticallyimport { betterAuth } from "@next-safe-action/adapter-better-auth";import { auth } from "./auth";
const authClient = actionClient.use(betterAuth(auth));

來源與署名

來源:next-safe-action/skills位於skills/safe-action-better-auth提交a2605bd

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架