插件
插件是 Better Auth 的核心组成部分,它们允许您扩展基础功能。您可以使用插件来添加新的身份验证方法、功能或自定义行为。
Better Auth 内置了许多即用型插件。详情请查看插件部分。您也可以创建自己的插件。
使用插件
插件可以是服务器端插件、客户端插件,或者两者兼备。
要在服务器端添加插件,请将其包含在 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 背后的同一团队开发的一个简单的 TypeScript 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: "Hello World"
})
})
}
} satisfies BetterAuthPlugin
}
创建 Auth 端点封装了 Better Call 中的 createEndpoint
。在 ctx
对象内部,它会提供另一个名为 context
的对象,让您可以访问 better-auth 特定的上下文,包括 options
、db
、baseURL
等。
上下文对象
appName
:应用程序名称。默认为 "Better Auth"。options
:传递给 Better Auth 实例的配置选项。tables
:核心表定义。它是一个对象,以表名为键,以表结构定义为值。baseURL
:认证服务器的 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 查询或出于性能原因,否则我们建议使用此选项而不是db
)。internalAdapter
:这些是 Better Auth 使用的内部数据库调用。例如,你可以使用这些调用来创建会话,而不是直接使用adapter
。例如:internalAdapter.createSession(userId)
。createAuthCookie
:这是一个辅助函数,允许你获取用于set
或get
cookie 的name
和options
。它实现了诸如基于 cookie 的__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
}
字段(Fields)
默认情况下,better-auth 会为每个表创建一个 id
字段。你可以通过向 fields
对象中添加字段来为表增加额外的列。
键(key)表示列名,值(value)表示列的定义。列定义可以包含以下属性:
type
:字段类型。可以是string
、number
、boolean
或date
。required
:是否在新建记录时该字段为必填项(默认值:false
)。unique
:该字段是否应具有唯一性(默认值:false
)。reference
:如果该字段是对另一个表的引用(默认值:null
)。它接受一个包含以下属性的对象:model
:引用的表名。field
:引用的字段名。onDelete
:当被引用的记录被删除时执行的操作(默认值:null
)。
其他 Schema 属性
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(钩子)
Hooks(钩子)用于在客户端或直接在服务器上执行某个操作之前或之后运行代码。你可以通过传递一个包含 before
和 after
属性的 hooks
对象来向服务器添加钩子。
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
对象,请求将被停止,响应将发送给客户端。
const myPlugin = ()=>{
return {
id: "my-plugin",
middlewares: [
{
path: "/my-plugin/hello-world",
middleware: createAuthMiddleware(async(ctx)=>{
// 执行某些操作
})
}
]
} satisfies BetterAuthPlugin
}
请求前与响应后
除了中间件之外,您还可以在请求发出前和响应返回后进行钩入。如果您想要影响所有请求或响应,这特别有用。
请求前
onRequest
函数在请求发出前被调用。它接收两个参数:request
和 context
对象。
其工作方式如下:
- 正常继续:如果您不返回任何内容,请求将照常进行。
- 中断请求:要停止请求并发送响应,请返回一个包含
response
属性的对象,该属性包含一个Response
对象。 - 修改请求:您还可以返回修改后的
request
对象,在发送前更改请求。
const myPlugin = ()=> {
return {
id: "my-plugin",
onRequest: async (request, context) => {
// 执行某些操作
},
} satisfies BetterAuthPlugin
}
响应处理
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
允许您通过传递 auth 中间件的 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 BetterAuthPlugin
sessionMiddleware
一个检查客户端是否具有有效会话的中间件。如果客户端具有有效会话,它会将会话数据添加到上下文对象中。
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: "Hello World"
})
})
}
} 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 调用之外的操作,可以灵活调整此规则。
获取原子状态 (Get Atoms)
这仅在您想要提供类似 useSession
这样的 hooks
时有用。
Get atoms 通过 better fetch 的 fetch
函数调用,它应该返回一个包含原子状态的对象。这些原子状态应使用 nanostores 创建。这些原子状态将由 nanostores 提供的各框架的 useStore
hook 来解析。
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
查看内置插件以了解如何正确使用原子状态的示例。
路径方法 (Path methods)
默认情况下,推断出的路径如果不需要请求体则使用 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 BetterAuthClientPlugin
Fetch 插件 (Fetch plugins)
如果您需要使用 better fetch 插件,可以将它们传递到 fetchPlugins
数组中。您可以在 better fetch 文档 中了解更多关于 better fetch 插件的信息。
原子状态监听器 (Atom Listeners)
这仅在您想要提供类似 useSession
这样的 hooks,并且希望监听原子状态并在其发生变化时重新评估它们时有用。
您可以在内置插件中查看其使用方法。