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 从公开仓库中收录这些内容。

举报或申请下架