mcpField
S
safe-action-better-auth

safe-action-better-auth

github.com/next-safe-action安全1482 次安装8 周前更新
代码仓库

关于 safe-action-better-auth

safe-action-better-auth 是为 next-safe-action 客户端接入 Better Auth 会话认证的适配器。它从请求中获取会话、拒绝未认证请求,并向 action 提供类型推断的认证上下文。

主要功能

  1. 01从请求标头获取会话
  2. 02拒绝未认证请求
  3. 03注入独立命名空间的认证上下文
  4. 04推断用户和会话类型
  5. 05支持自定义授权回调

适用场景

  1. 01用 Better Auth 保护 Next.js Server Action
  2. 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() 链创建一个预验证中间件:

  1. 获取会话:使用来自 next/headers 的请求标头,调用 auth.api.getSession({ headers: await headers() })
  2. 阻止未经过身份验证的请求:当会话不存在时,调用 next/navigation 中的 unauthorized()
  3. 注入类型化上下文:将 { auth: { user, session } } 传递给 next(),并将其合并到操作上下文中

上下文会放在 auth 命名空间下,以避免与其他中间件上下文属性冲突。

类型推断

中间件会从你的 Better Auth 实例推断出精确的 user 和 session 类型,包括插件添加的任何字段。例如,如果你使用 organization 插件,ctx.auth.session 将包含 activeOrganizationId。无需手动添加类型注解。

入口点

入口点导出项环境
@next-safe-action/adapter-better-authbetterAuth、类型服务端

导出的类型

类型描述
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