插件
了解如何在 Better Auth 中使用插件。
插件是 Better Auth 的关键部分,它们允许您扩展基础功能。您可以使用插件添加新的身份验证方法、功能,或者自定义行为。
Better Auth 自带许多内置插件,随时可用。请查看插件章节了解详情。您也可以创建自己的插件。
使用插件
插件可以是服务器端插件、客户端插件,或者两者兼有。
要在服务器端添加插件,将其包含在认证配置的 plugins 数组中。插件将使用提供的选项进行初始化。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
plugins: [
// 在此添加您的插件
]
});客户端插件在创建客户端时添加。大多数插件需要服务器端和客户端插件共同配合才能正常工作。
前端的 Better Auth 认证客户端使用 better-auth/client 提供的 createAuthClient 函数。
import { createAuthClient } from "better-auth/client";
const authClient = createAuthClient({
plugins: [
// 在此添加您的客户端插件
]
});我们建议将 auth-client 和您的普通认证实例保存在不同的文件中。
创建插件
首先,您需要一个服务器插件。 服务器插件是所有插件的核心,客户端插件提供与前端 API 的接口,便于与服务器插件协作。
如果您的服务器插件有需要从客户端调用的端点,还需要创建客户端插件。
插件能做什么?
- 创建自定义的
endpoint执行任意操作。 - 使用自定义的
schemas扩展数据库表。 - 使用
middleware针对一组路由,利用路由匹配器,仅在通过请求调用这些路由时运行。 - 使用
hooks针对特定路由或请求。如果您希望即使直接调用端点也能执行钩子。 - 如果想做影响所有请求或响应的事情,可以使用
onRequest或onResponse。 - 创建自定义的
rate-limit规则。
创建服务器插件
创建服务器插件时,需要传入满足 BetterAuthPlugin 接口的对象。
唯一必填属性是 id,它是插件唯一标识符。
服务器端与客户端插件可以使用相同的 id。
import type { BetterAuthPlugin } from "better-auth";
export const myPlugin = () => {
return {
id: "my-plugin",
} satisfies BetterAuthPlugin
}您不需要将插件写成函数,但推荐这么做。这样可以向插件传递选项,与内置插件保持一致。
端点
要向服务器添加端点,可以传入 endpoints,它是一个对象,键是任意字符串,值为 AuthEndpoint。
创建 Auth 端点需要从 better-auth 导入 createAuthEndpoint。
Better Auth 封装了另一个名为 Better Call 的库来创建端点。Better Call 是由 Better Auth 团队开发的简单 TS Web 框架。
import { createAuthEndpoint } from "better-auth/api";
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = () => {
return {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
}, async(ctx) => {
return ctx.json({
message: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}创建 Auth 端点实际上封装了 Better Call 的 createEndpoint。在 ctx 对象内部,还会提供一个名为 context 的对象,供您访问 Better Auth 特定的上下文,包括 options、db、baseURL 等。
Context 对象
appName:应用名称。默认为 "Better Auth"。options:传入 Better Auth 实例的选项。tables:核心表定义。是一个以表名为键、以模式定义为值的对象。baseURL:认证服务器基本地址,包含路径。例如,如果服务器运行在http://localhost:3000,则默认baseURL为http://localhost:3000/api/auth,除非用户更改。session:会话配置。包括updateAge和expiresIn。secret:用于各种目的的密钥,由用户定义。authCookie:核心认证 Cookie 的默认配置。logger:Better Auth 使用的日志实例。db:Better Auth 使用的 Kysely 实例,用于数据库交互。adapter:与db相似,但提供 ORM 风格的函数操作数据库。(推荐使用此,除非需要原始 SQL 查询或性能需求)internalAdapter:Better Auth 内部数据库调用。例如,您可通过它创建会话,而不是直接使用adapter,如internalAdapter.createSession(userId)。createAuthCookie:辅助函数,根据是否安全连接(HTTPS)或生产环境,实现了如__Secure-前缀,为设置或获取 Cookie 返回名称及配置。trustedOrigins:您通过options.trustedOrigins配置的受信任来源列表。isTrustedOrigin:辅助函数,用于快速判断指定 URL 或路径是否基于受信任来源配置被信任。
其他属性,您可以参考 Better Call 文档和 源码。
端点规则
- 确保端点路径采用 kebab-case(连字符小写)风格
- 端点方法只允许使用
POST或GET - 修改数据的接口应使用
POST - 获取数据的接口应使用
GET - 必须使用
createAuthEndpoint创建 API 端点 - 端点路径应保证唯一,避免与其他插件冲突。如果使用常见路径,请在路径前加上插件名作为前缀。例如
/my-plugin/hello-world而非/hello-world。
Schema(数据库模式)
您可以通过传入 schema 对象定义插件的数据库模式。schema 对象以表名为键,模式定义为值。
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = () => {
return {
id: "my-plugin",
schema: {
myTable: {
fields: {
name: {
type: "string"
}
},
modelName: "myTable" // 如需与键名不同,可指定,否则可省略
}
}
} satisfies BetterAuthPlugin
}字段
默认 Better Auth 会为每张表创建一个 id 字段。您可以通过向 fields 对象添加字段,自定义额外列。
字段名作为键,字段定义为值。定义属性包括:
type:字段类型,可选string、number、boolean、daterequired:是否为必填字段,(默认true)unique:字段是否唯一,(默认false)references:字段是否为关联另一张表的外键(可选),对象格式包含:model:所关联表名field:所关联字段名onDelete:关联记录删除时操作(默认cascade)
其他 Schema 属性
disableMigration:是否禁用同步更新(迁移),默认false
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = (opts: PluginOptions) => {
return {
id: "my-plugin",
schema: {
rateLimit: {
fields: {
key: {
type: "string",
},
},
disableMigration: opts.storage.provider !== "database",
},
},
} satisfies BetterAuthPlugin
}如果您在 user 或 session 表添加额外字段,getSession 和 signUpEmail 调用时类型会自动推断。
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = () => {
return {
id: "my-plugin",
schema: {
user: {
fields: {
age: {
type: "number",
},
},
},
},
} satisfies BetterAuthPlugin
}这样会为 user 表添加 age 字段,所有返回 user 的接口也会包含此字段,且 TypeScript 会正确推断。
请勿在 user 或 session 表存储敏感信息。如需存储敏感信息,请创建新表。
Hooks(钩子)
钩子用于在操作执行前后运行代码,无论是从客户端还是直接在服务器上执行。您可以通过传入 hooks 对象添加钩子,该对象须包含 before 和 after 属性。
import { createAuthMiddleware } from "better-auth/plugins";
const myPlugin = () => {
return {
id: "my-plugin",
hooks: {
before: [{
matcher: (context) => {
return context.headers.get("x-my-header") === "my-value"
},
handler: createAuthMiddleware(async (ctx) => {
// 请求前做一些操作
return {
context: ctx // 如需修改上下文
}
})
}],
after: [{
matcher: (context) => {
return context.path === "/sign-up/email"
},
handler: createAuthMiddleware(async (ctx) => {
return ctx.json({
message: "Hello World"
}) // 如需修改响应
})
}]
}
} satisfies BetterAuthPlugin
}中间件
您可以通过传入 middlewares 数组给服务器添加中间件。数组中每项为中间件对象,包含 path 和 middleware 属性。与钩子不同,中间件仅在客户端发起的 API 请求时执行,若直接调用端点,中间件不会运行。
path 可以为字符串或路径匹配器,使用与 better-call 相同的路径匹配系统。
中间件内若抛出 APIError 或返回 Response 对象,请求将停止,响应发送给客户端。
import type { BetterAuthPlugin } from "better-auth";
import { createAuthMiddleware } from "better-auth/api";
const myPlugin = () => {
return {
id: "my-plugin",
middlewares: [
{
path: "/my-plugin/hello-world",
middleware: createAuthMiddleware(async(ctx) => {
// 执行操作
})
}
]
} satisfies BetterAuthPlugin
}请求前与响应后钩子
除了中间件外,您还可以钩入请求发起前和响应返回后。主要用于影响所有请求或响应。
请求前
onRequest 函数在请求发起前执行,接受两个参数:request 和 context。
用法示例:
- 继续正常流程:不返回任何值,请求照常进行。
- 中断请求:返回含有
response属性的对象,response为Response实例,则请求被中断,且发送对应响应。 - 修改请求:返回修改后的
request对象以更改请求。
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = () => {
return {
id: "my-plugin",
onRequest: async (request, context) => {
// 执行操作
},
} satisfies BetterAuthPlugin
}响应后
onResponse 函数在响应返回后立即执行,接受两个参数:response 和 context。
用法示例:
- 修改响应:返回修改后的响应对象以更改发送给客户端的内容。
- 继续正常:不返回任何内容,响应保持不变。
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = () => {
return {
id: "my-plugin",
onResponse: async (response, context) => {
// 执行操作
},
} satisfies BetterAuthPlugin
}限速规则
您可以通过传入 rateLimit 数组定义自定义限速规则。数组项为限速对象。
import type { BetterAuthPlugin } from "better-auth";
const myPlugin = () => {
return {
id: "my-plugin",
rateLimit: [
{
pathMatcher: (path) => {
return path === "/my-plugin/hello-world"
},
limit: 10,
window: 60,
}
]
} satisfies BetterAuthPlugin
}受信任来源
如果您正在构建自定义插件或端点,可以使用认证上下文中的 isTrustedOrigin() 方法根据受信任来源配置验证 URL。这样确保自定义端点遵循与 Better Auth 内置端点相同的安全设置。
import type { BetterAuthPlugin } from "better-auth";
import { createAuthEndpoint, APIError } from "better-auth/api";
import * as z from "zod"
const myPlugin = () => {
return {
id: "my-plugin",
trustedOrigins: [
"http://trusted.com"
],
endpoints: {
getTrustedHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
query: z.object({
url: z.string()
}),
}, async (ctx) => {
// allowRelativePaths 选项用于允许或禁止相对路径
if (!ctx.context.isTrustedOrigin(ctx.query.url, { allowRelativePaths: false })) {
throw new APIError("FORBIDDEN", {
message: "origin is not trusted."
});
}
return ctx.json({
message: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}更多信息请参见受信任来源和安全文档。
服务器插件辅助函数
下面是一些创建服务器插件的辅助函数。
getSessionFromCtx
通过传入认证中间件的 context,可获取客户端会话数据。
import type { BetterAuthPlugin } from "better-auth";
import { createAuthMiddleware } from "better-auth/plugins";
import { getSessionFromCtx } from "better-auth/api";
const myPlugin = {
id: "my-plugin",
hooks: {
before: [{
matcher: (context) => {
return context.headers.get("x-my-header") === "my-value"
},
handler: createAuthMiddleware(async (ctx) => {
const session = await getSessionFromCtx(ctx);
// 操作客户端的会话数据
return {
context: ctx
}
})
}],
}
} satisfies BetterAuthPluginsessionMiddleware
此中间件用于检查客户端是否具有有效会话,若有效,则会将会话数据添加到上下文对象中。
import type { BetterAuthPlugin } from "better-auth";
import { createAuthEndpoint, sessionMiddleware } from "better-auth/api";
const myPlugin = () => {
return {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
use: [sessionMiddleware],
}, async (ctx) => {
const session = ctx.context.session;
return ctx.json({
message: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}创建客户端插件
如果您的端点需要从客户端调用,也需创建客户端插件。Better Auth 客户端能根据服务器插件推断端点,也可添加额外的客户端逻辑。
import type { BetterAuthClientPlugin } from "better-auth/client";
export const myPluginClient = () => {
return {
id: "my-plugin",
} satisfies BetterAuthClientPlugin
}端点接口
客户端插件添加 $InferServerPlugin 字段,用于推断服务器端插件的端点。
客户端将路径作为对象,并将 kebab-case 转换为 camelCase。例如,/my-plugin/hello-world 转为 myPlugin.helloWorld。
import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
const myPluginClient = () => {
return {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
} satisfies BetterAuthClientPlugin
}获取 actions
如需为客户端添加额外方法,可使用 getActions 函数,它接收客户端的 fetch 函数。
Better Auth 使用 Better Fetch 进行请求,它是作者开发的简单 fetch 包装器。
import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
import type { BetterFetchOption } from "@better-fetch/fetch";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
getActions: ($fetch) => {
return {
myCustomAction: async (data: {
foo: string,
}, fetchOptions?: BetterFetchOption) => {
const res = $fetch("/custom/action", {
method: "POST",
body: {
foo: data.foo
},
...fetchOptions
})
return res
}
}
}
} satisfies BetterAuthClientPlugin作为一般指导,确保每个函数只接受一个参数,第二个参数为可选的 fetchOptions,以让用户传递额外的 fetch 选项。函数应返回包含 data 和 error 键的对象。
如您的用例不局限于 API 调用,可灵活调整此规则。
获取 Atom
此功能仅当您想提供如 useSession 这类钩子时有用。
getAtoms 函数接收 Better Fetch 的 fetch,返回包含各个 Atom 的对象。Atom 应使用 nanostores 创建。Atom 会被各框架基于 nanostores 的 useStore 钩子自动解析。
import { atom } from "nanostores";
import type { BetterAuthClientPlugin } from "better-auth/client";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
getAtoms: ($fetch) => {
const myAtom = atom<null>()
return {
myAtom
}
}
} satisfies BetterAuthClientPlugin请参阅内置插件了解如何正确使用 atoms。
路径方法
默认情况下,推断的路径如果不需要请求体则使用 GET,否则使用 POST。您可以通过传入 pathMethods 对象覆盖,键为路径,值为方法("POST" | "GET")。
import type { BetterAuthClientPlugin } from "better-auth/client";
import type { myPlugin } from "./plugin";
const myPluginClient = {
id: "my-plugin",
$InferServerPlugin: {} as ReturnType<typeof myPlugin>,
pathMethods: {
"/my-plugin/hello-world": "POST"
}
} satisfies BetterAuthClientPluginFetch 插件
如需使用 Better Fetch 插件,可以传入 fetchPlugins 数组。更多信息请参阅 Better Fetch 文档。
Atom 监听器
此功能只有在您想提供如 useSession 的钩子,并且希望监听 atoms 并在改变时重新计算时才有用。
内置插件中有示例展示了此用法。