插件
插件是 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 和您的正常 auth 实例保存在单独的文件中。
创建插件
要开始,您需要一个服务器端插件。 服务器端插件是所有插件的骨干,而客户端插件则用于提供与前端 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,它需要一个对象,其中键为任意 string,值为 AuthEndpoint。
要创建 Auth 端点,您需要从 better-auth 导入 createAuthEndpoint。
Better Auth 使用围绕另一个名为 Better Call 的库进行包装来创建端点。Better Call 是由 Better Auth 背后的同一团队制作的简单 ts Web 框架。
import { createAuthEndpoint } from "better-auth/api";
const myPlugin = ()=> {
return {
id: "my-plugin",
endpoints: {
getHelloWorld: createAuthEndpoint("/my-plugin/hello-world", {
method: "GET",
}, async(ctx) => {
return ctx.json({
message: "你好世界"
})
})
}
} satisfies BetterAuthPlugin
}创建 Auth 端点围绕 Better Call 中的 createEndpoint。在 ctx 对象内部,它将提供另一个名为 context 的对象,该对象为您提供 better-auth 特定上下文的访问,包括 options、db、baseURL 等。
Context 对象
appName: 应用程序的名称。默认为 "Better Auth"。options: 传递给 Better Auth 实例的选项。tables: 核心表定义。它是一个对象,其中键为表名,值为架构定义。baseURL: 认证服务器的 baseURL。这包括路径。例如,如果服务器运行在http://localhost:3000上,则 baseURL 默认为http://localhost:3000/api/auth,除非用户更改。session: 会话配置。包括updateAge和expiresIn值。secret: 用于各种目的的密钥。这是用户定义的。authCookie: 核心认证 cookie 的默认 cookie 配置。logger: Better Auth 使用的日志记录器实例。db: Better Auth 用于与数据库交互的 Kysely 实例。adapter: 这与 db 相同,但它为您提供类似orm的函数来与数据库交互。(我们推荐使用这个而不是db,除非您需要原始 SQL 查询或出于性能原因)internalAdapter: 这些是 Better Auth 使用的内部 db 调用。例如,您可以使用这些调用来创建会话,而不是直接使用adapter。internalAdapter.createSession(userId)createAuthCookie: 这是一个辅助函数,它让您获取 cookie 的name和options,以便set或getcookie。它实现了诸如__secure前缀和基于的__host前缀等功能
对于其他属性,您可以查看 Better Call 文档和 源代码 。
端点规则
- 确保为端点路径使用 kebab-case
- 确保仅为端点使用
POST或GET方法。 - 任何修改数据的函数都应为
POST方法。 - 任何获取数据的函数都应为
GET方法。 - 确保使用
createAuthEndpoint函数创建 API 端点。 - 确保您的路径是唯一的,以避免与其他插件冲突。如果您使用常见路径,请将插件名称作为路径的前缀。(使用
/my-plugin/hello-world而不是/hello-world。)
架构
您可以通过传递 schema 对象为插件定义数据库架构。架构对象应以表名为键,以架构定义为值。
import { BetterAuthPlugin } from "better-auth/plugins";
const myPlugin = ()=> {
return {
id: "my-plugin",
schema: {
myTable: {
fields: {
name: {
type: "string"
}
},
modelName: "myTable" // 如果您想使用与键不同的名称,则可选
}
}
} satisfies BetterAuthPlugin
}字段
默认情况下,better-auth 将为每个表创建一个 id 字段。您可以通过将它们添加到 fields 对象中为表添加额外字段。
键是列名,值是列定义。列定义可以具有以下属性:
type: 字段的类型。它可以是 string、number、boolean、date。
required: 如果新记录中该字段是否应为必需的。(默认:false)
unique: 如果该字段是否应为唯一的。(默认:false)
reference: 如果该字段是到另一个表的引用。(默认:null)它接受一个具有以下属性的对象:
model: 要引用的表名。field: 要引用的字段名。onDelete: 当引用的记录被删除时要采取的操作。(默认:null)
其他架构属性
disableMigration: 如果不应迁移该表。(默认:false)
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 调用中推断。
const myPlugin = ()=>{
return {
id: "my-plugin",
schema: {
user: {
fields: {
age: {
type: "number",
},
},
},
},
} satisfies BetterAuthPlugin
}这将向 user 表添加一个 age 字段,并且所有返回 user 的端点将包含 age 字段,并且它将被 TypeScript 正确推断。
不要在 user 或 session 表中存储敏感信息。如果需要存储敏感信息,请创建一个新表。
钩子
钩子用于在执行操作之前或之后运行代码,该操作可以来自客户端或直接在服务器上。您可以通过传递 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: "你好世界"
}) // 如果您想修改响应
})
}]
}
} satisfies BetterAuthPlugin
}中间件
您可以通过传递 middlewares 数组向服务器添加中间件。此数组应包含中间件对象,每个对象具有 path 和 middleware 属性。与钩子不同,中间件仅在来自客户端的 api 请求上运行。如果端点被直接调用,则中间件不会运行。
path 可以是字符串或路径匹配器,使用与 better-call 相同的路径匹配系统。
如果您从中间件抛出 APIError 或返回 Response 对象,则请求将被停止并将响应发送到客户端。
const myPlugin = ()=>{
return {
id: "my-plugin",
middlewares: [
{
path: "/my-plugin/hello-world",
middleware: createAuthMiddleware(async(ctx)=>{
//做某事
})
}
]
} satisfies BetterAuthPlugin
}On Request 和 On Response
除了中间件之外,您还可以钩入请求发出之前和响应返回之后的时刻。这主要用于如果您想执行影响所有请求或响应的操作。
On Request
onRequest 函数在请求发出之前立即调用。它接受两个参数:request 和 context 对象。
它的工作方式如下:
- 正常继续:如果您不返回任何内容,请求将按常规进行。
- 中断请求:要停止请求并发送响应,请返回一个包含
Response对象的response属性的对象。 - 修改请求:您也可以返回修改后的
request对象,以在发送之前更改请求。
const myPlugin = ()=> {
return {
id: "my-plugin",
onRequest: async (request, context) => {
//做某事
},
} satisfies BetterAuthPlugin
}On Response
onResponse 函数在响应返回后立即执行。它接受两个参数:response 和 context 对象。
使用它的方式如下:
- 修改响应:您可以返回修改后的响应对象,以在发送到客户端之前更改响应。
- 正常继续:如果您不返回任何内容,响应将按原样发送。
const myPlugin = ()=>{
return {
id: "my-plugin",
onResponse: async (response, context) => {
//做某事
},
} satisfies BetterAuthPlugin
}速率限制
您可以通过传递 rateLimit 数组为插件定义自定义速率限制规则。速率限制数组应包含速率限制对象的数组。
const myPlugin = ()=>{
return {
id: "my-plugin",
rateLimit: [
{
pathMatcher: (path)=>{
return path === "/my-plugin/hello-world"
},
limit: 10,
window: 60,
}
]
} satisfies BetterAuthPlugin
}服务器端插件辅助函数
创建服务器端插件的一些额外辅助函数。
getSessionFromCtx
允许您通过传递认证中间件的 context 获取客户端的会话数据。
import { createAuthMiddleware } from "better-auth/plugins";
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 { createAuthMiddleware } from "better-auth/plugins";
import { 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: "你好世界"
})
})
}
} satisfies BetterAuthPlugin
}创建客户端插件
如果您的端点需要从客户端调用,您还需要创建客户端插件。Better Auth 客户端可以从服务器端插件推断端点。您也可以添加额外的客户端逻辑。
import type { BetterAuthClientPlugin } from "better-auth";
export const myPluginClient = ()=>{
return {
id: "my-plugin",
} satisfies BetterAuthClientPlugin
}端点接口
端点通过向客户端插件添加 $InferServerPlugin 键从服务器端插件推断。
客户端将 path 推断为对象,并将 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
}获取操作
如果您需要向客户端添加额外方法或其他内容,您可以使用 getActions 函数。此函数使用来自客户端的 fetch 函数调用。
Better Auth 使用 Better fetch 进行请求。Better fetch 是由 Better Auth 的同一作者制作的简单 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 调用的操作,请随意偏离此规则。
获取原子
这仅在您想提供类似 useSession 的 hooks 时有用。
获取原子使用来自 better fetch 的 fetch 函数调用,并且它应返回一个包含原子的对象。原子应使用 nanostores 创建。原子将由 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请参阅内置插件以获取如何正确使用原子的示例。
路径方法
默认情况下,推断的路径如果不需要主体则使用 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 文档 中阅读有关 better fetch 插件的更多信息。
原子监听器
这仅在您想提供类似 useSession 的 hooks 并且想监听原子并在它们更改时重新评估它们时有用。
您可以在内置插件中看到这是如何使用的。