关于 safe-action-better-auth
safe-action-better-auth 是为 next-safe-action 客户端接入 Better Auth 会话认证的适配器。它从请求中获取会话、拒绝未认证请求,并向 action 提供类型推断的认证上下文。
主要功能
- 01从请求标头获取会话
- 02拒绝未认证请求
- 03注入独立命名空间的认证上下文
- 04推断用户和会话类型
- 05支持自定义授权回调
适用场景
- 01用 Better Auth 保护 Next.js Server Action
- 02在 action 处理器中访问类型化会话数据
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ next-safe-action/ skills --skill safe-action-better-auth安装后在 Claude Code 中输入/safe-action-better-auth确认已加载。
附带文件authorize-patterns.md
详细介绍
next-safe-action Better Auth 适配器
安装
npm install @next-safe-action/adapter-better-auth better-auth
导入
import { betterAuth } from "@next-safe-action/adapter-better-auth";
快速开始
1. 设置 Better Auth
创建 Better Auth 服务实例。如果你的操作需要设置 Cookie(例如 signInEmail、signUpEmail),请添加 nextCookies() 插件:
// src/lib/auth.ts
import { 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. 在 Next.js 中启用身份验证中断
默认行为会使用 next/navigation 中的 unauthorized(),这需要启用此标志:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
};
export default nextConfig;
3. 创建经过身份验证的操作客户端
// src/lib/safe-action.ts
import { 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 client
export const authClient = actionClient.use(betterAuth(auth));
4. 在操作中使用
// 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 };
});
工作原理
betterAuth() 会为安全操作客户端的 .use() 链创建一个预验证中间件:
- 获取会话:使用来自
next/headers的请求标头,调用auth.api.getSession({ headers: await headers() }) - 阻止未经过身份验证的请求:当会话不存在时,调用
next/navigation中的unauthorized() - 注入类型化上下文:将
{ auth: { user, session } }传递给next(),并将其合并到操作上下文中
上下文会放在 auth 命名空间下,以避免与其他中间件上下文属性冲突。
类型推断
中间件会从你的 Better Auth 实例推断出精确的 user 和 session 类型,包括插件添加的任何字段。例如,如果你使用 organization 插件,ctx.auth.session 将包含 activeOrganizationId。无需手动添加类型注解。
入口点
| 入口点 | 导出项 | 环境 |
|---|---|---|
@next-safe-action/adapter-better-auth | betterAuth、类型 | 服务端 |
导出的类型
| 类型 | 描述 |
|---|---|
BetterAuthContext<O> | 中间件添加的上下文结构:{ auth: { user, session } }。类型通过 Auth<O>["$Infer"]["Session"] 从 Better Auth 实例推断。 |
AuthorizeFn<O, NC, Ctx> | authorize 回调的签名。接收 { authData, ctx, next }。 |
BetterAuthOpts<O, NC, Ctx> | betterAuth() 的选项对象类型。包含可选的 authorize 回调。 |
与手动身份验证中间件的区别
如果你使用 Better Auth,建议使用 betterAuth(auth),而不是手动编写身份验证中间件。此适配器会自动处理会话获取、Cookie 集成、类型处理和未授权请求拒绝。
// Manual — don't do this if you have @next-safe-action/adapter-better-auth installed
const 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 instead
const authClient = actionClient.use(betterAuth(auth));
// ctx.auth.user and ctx.auth.session are fully typed automatically
相关文档
反模式
// BAD: Missing nextCookies() plugin — cookies won't be set in Server Actions
// Session will silently be null when actions try to set cookies
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [/* no nextCookies() */],
});
// GOOD: Add nextCookies() as the last plugin
import { betterAuth } from "better-auth";
import { nextCookies } from "better-auth/next-js";
export const auth = betterAuth({
plugins: [
// ...other plugins
nextCookies(), // must be last
],
});
// BAD: Missing authInterrupts flag — unauthorized() will throw a runtime error
// next.config.ts
const nextConfig: NextConfig = {};
// GOOD: Enable authInterrupts
const nextConfig: NextConfig = {
experimental: {
authInterrupts: true,
},
};
// BAD: Re-fetching session inside authorize — it's already pre-fetched as authData
actionClient.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 directly
actionClient.use(
betterAuth(auth, {
authorize: ({ authData, next }) => {
if (!authData || authData.user.role !== "admin") {
unauthorized();
}
return next({ ctx: { auth: authData } });
},
})
);
// BAD: Writing manual Better Auth middleware when the adapter is installed
import { 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() automatically
import { betterAuth } from "@next-safe-action/adapter-better-auth";
import { auth } from "./auth";
const authClient = actionClient.use(betterAuth(auth));
评论
支持 Markdown