数据库
适配器
Better Auth 需要数据库连接来存储数据。数据库将用于存储诸如用户、会话等数据。插件也可以定义自己的数据库表来存储数据。
您可以通过在数据库选项中传递支持的数据库实例来向 Better Auth 传递数据库连接。您可以在其他关系型数据库文档中了解有关支持的数据库适配器的更多信息。
CLI
Better Auth 附带一个 CLI 工具,用于管理数据库迁移和生成架构。
运行迁移
CLI 会检查您的数据库,并提示您添加缺失的表或使用新列更新现有表。这仅支持内置的 Kysely 适配器。对于其他适配器,您可以使用 generate 命令来创建架构,并通过您的 ORM 处理迁移。
npx @better-auth/cli migrate生成架构
Better Auth 还提供了一个 generate 命令来生成 Better Auth 所需的架构。generate 命令会创建 Better Auth 所需的架构。如果您使用 Prisma 或 Drizzle 等数据库适配器,此命令将为您的 ORM 生成正确的架构。如果您使用内置的 Kysely 适配器,它将生成一个 SQL 文件,您可以直接在数据库上运行。
npx @better-auth/cli generate有关 CLI 的更多信息,请参阅CLI文档。
如果您更喜欢手动添加表,也可以这样做。Better Auth 所需的核心架构如以下所述,您可以在插件文档中找到插件所需的附加架构。
辅助存储
Better Auth 中的辅助存储允许您使用键值存储来管理会话数据、速率限制计数器等。当您希望将这些密集记录的存储卸载到高性能存储甚至 RAM 时,这会很有用。
实现
要使用辅助存储,请实现 SecondaryStorage 接口:
interface SecondaryStorage {
get: (key: string) => Promise<unknown>;
set: (key: string, value: string, ttl?: number) => Promise<void>;
delete: (key: string) => Promise<void>;
}然后,将您的实现提供给 betterAuth 函数:
betterAuth({
// ... other options
secondaryStorage: {
// Your implementation here
},
});示例:Redis 实现
这是一个使用 Redis 的基本示例:
import { createClient } from "redis";
import { betterAuth } from "better-auth";
const redis = createClient();
await redis.connect();
export const auth = betterAuth({
// ... other options
secondaryStorage: {
get: async (key) => {
return await redis.get(key);
},
set: async (key, value, ttl) => {
if (ttl) await redis.set(key, value, { EX: ttl });
// or for ioredis:
// if (ttl) await redis.set(key, value, 'EX', ttl)
else await redis.set(key, value);
},
delete: async (key) => {
await redis.del(key);
}
}
});此实现允许 Better Auth 使用 Redis 存储会话数据和速率限制计数器。您还可以为键名添加前缀。
核心架构
Better Auth 需要数据库中存在以下表。类型采用 typescript 格式。您可以在数据库中使用相应的类型。
用户
表名:user
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 每个用户的唯一标识符 | |
| name | string | - | 用户选择的显示名称 |
| string | - | 用于通信和登录的用户电子邮件地址 | |
| emailVerified | boolean | - | 用户电子邮件是否已验证 |
| image | string | 用户的图像 URL | |
| createdAt | Date | - | 用户账户创建的时间戳 |
| updatedAt | Date | - | 用户信息的最后更新时间戳 |
会话
表名:session
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 每个会话的唯一标识符 | |
| userId | string | 用户 ID | |
| token | string | - | 唯一的会话令牌 |
| expiresAt | Date | - | 会话过期的时间 |
| ipAddress | string | 设备的 IP 地址 | |
| userAgent | string | 设备的用户代理信息 | |
| createdAt | Date | - | 会话创建的时间戳 |
| updatedAt | Date | - | 会话更新的时间戳 |
账户
表名:account
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 每个账户的唯一标识符 | |
| userId | string | 用户 ID | |
| accountId | string | - | 由 SSO 提供的账户 ID,或对于凭证账户等于 userId |
| providerId | string | - | 提供商 ID |
| accessToken | string | 账户的访问令牌。由提供商返回 | |
| refreshToken | string | 账户的刷新令牌。由提供商返回 | |
| accessTokenExpiresAt | Date | 访问令牌过期的时间 | |
| refreshTokenExpiresAt | Date | 刷新令牌过期的时间 | |
| scope | string | 账户的范围。由提供商返回 | |
| idToken | string | 从提供商返回的 ID 令牌 | |
| password | string | 账户的密码。主要用于电子邮件和密码认证 | |
| createdAt | Date | - | 账户创建的时间戳 |
| updatedAt | Date | - | 账户更新的时间戳 |
验证
表名:verification
| Field Name | Type | Key | Description |
|---|---|---|---|
| id | string | 每个验证的唯一标识符 | |
| identifier | string | - | 验证请求的标识符 |
| value | string | - | 要验证的值 |
| expiresAt | Date | - | 验证请求过期的时间 |
| createdAt | Date | - | 验证请求创建的时间戳 |
| updatedAt | Date | - | 验证请求更新的时间戳 |
自定义表
Better Auth 允许您自定义核心架构的表名和列名。您还可以通过向用户和会话表添加附加字段来扩展核心架构。
自定义表名
您可以通过在认证配置中使用 modelName 和 fields 属性来自定义核心架构的表名和列名:
export const auth = betterAuth({
user: {
modelName: "users",
fields: {
name: "full_name",
email: "email_address",
},
},
session: {
modelName: "user_sessions",
fields: {
userId: "user_id",
},
},
});您的代码中的类型推断仍将使用原始字段名(例如,
user.name,而不是 user.full_name)。
要自定义插件的表名和列名,您可以在插件配置中使用 schema 属性:
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
twoFactor({
schema: {
user: {
fields: {
twoFactorEnabled: "two_factor_enabled",
secret: "two_factor_secret",
},
},
},
}),
],
});扩展核心架构
Better Auth 提供了一种类型安全的方式来扩展 user 和 session 架构。您可以向认证配置添加自定义字段,CLI 将自动更新数据库架构。这些附加字段将在诸如 useSession、signUp.email 和其他处理用户或会话对象的端点等函数中正确推断。
要添加自定义字段,请在认证配置的 user 或 session 对象中使用 additionalFields 属性。additionalFields 对象使用字段名作为键,每个值是一个 FieldAttributes 对象,其中包含:
type:字段的数据类型(例如,"string"、"number"、"boolean")。required:指示字段是否为必填的布尔值。defaultValue:字段的默认值(注意:这仅适用于 JavaScript 层;在数据库中,字段将是可选的)。input:这决定了在创建新记录时是否可以提供值(默认:true)。如果有附加字段,如role,不应在注册期间由用户提供,您可以将此设置为false。
以下是如何使用附加字段扩展用户架构的示例:
import { betterAuth } from "better-auth";
export const auth = betterAuth({
user: {
additionalFields: {
role: {
type: "string",
required: false,
defaultValue: "user",
input: false, // don't allow user to set role
},
lang: {
type: "string",
required: false,
defaultValue: "en",
},
},
},
});现在,您可以在应用程序逻辑中访问附加字段。
//on signup
const res = await auth.api.signUpEmail({
email: "test@example.com",
password: "password",
name: "John Doe",
lang: "fr",
});
//user object
res.user.role; // > "admin"
res.user.lang; // > "fr"有关如何在客户端推断附加字段的更多信息,请参阅 TypeScript 文档。
如果您使用社交 / OAuth 提供商,您可能希望提供 mapProfileToUser 来将配置文件数据映射到用户对象。这样,您就可以从提供商的配置文件中填充附加字段。
示例:为 firstName 和 lastName 映射配置文件到用户
import { betterAuth } from "better-auth";
export const auth = betterAuth({
socialProviders: {
github: {
clientId: "YOUR_GITHUB_CLIENT_ID",
clientSecret: "YOUR_GITHUB_CLIENT_SECRET",
mapProfileToUser: (profile) => {
return {
firstName: profile.name.split(" ")[0],
lastName: profile.name.split(" ")[1],
};
},
},
google: {
clientId: "YOUR_GOOGLE_CLIENT_ID",
clientSecret: "YOUR_GOOGLE_CLIENT_SECRET",
mapProfileToUser: (profile) => {
return {
firstName: profile.given_name,
lastName: profile.family_name,
};
},
},
},
});ID 生成
默认情况下,Better Auth 将为用户、会话和其他实体生成唯一 ID。如果您想自定义 ID 的生成方式,可以在认证配置的 advanced.database.generateId 选项中配置。
您也可以通过将 advanced.database.generateId 选项设置为 false 来禁用 ID 生成。这将假设您的数据库会自动生成 ID。
示例:自动数据库 ID
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: {
db: db,
},
advanced: {
database: {
generateId: false,
},
},
});数据库钩子
数据库钩子允许您定义自定义逻辑,这些逻辑可以在 Better Auth 中的核心数据库操作的生命周期中执行。您可以为以下模型创建钩子:user、session 和 account。
您可以定义两种类型的钩子:
1. Before Hook
- 目的:此钩子在相应实体(用户、会话或账户)创建或更新之前调用。
- 行为:如果钩子返回
false,操作将被中止。如果它返回数据对象,它将替换原始负载。
2. After Hook
- 目的:此钩子在相应实体创建或更新之后调用。
- 行为:您可以在实体成功创建或更新后执行附加操作或修改。
示例用法
import { betterAuth } from "better-auth";
export const auth = betterAuth({
databaseHooks: {
user: {
create: {
before: async (user, ctx) => {
// Modify the user object before it is created
return {
data: {
...user,
firstName: user.name.split(" ")[0],
lastName: user.name.split(" ")[1],
},
};
},
after: async (user) => {
//perform additional actions, like creating a stripe customer
},
},
},
},
});抛出错误
如果您想停止数据库钩子继续执行,可以使用从 better-auth/api 导入的 APIError 类来抛出错误。
import { betterAuth } from "better-auth";
import { APIError } from "better-auth/api";
export const auth = betterAuth({
databaseHooks: {
user: {
create: {
before: async (user, ctx) => {
if (user.isAgreedToTerms === false) {
// Your special condition.
// Send the API error.
throw new APIError("BAD_REQUEST", {
message: "User must agree to the TOS before signing up.",
});
}
return {
data: user,
};
},
},
},
},
});使用上下文对象
钩子作为第二个参数传递的上下文对象 (ctx) 包含有用信息。对于 update 钩子,这包括当前的 session,您可以使用它来访问登录用户的详细信息。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
databaseHooks: {
user: {
update: {
before: async (data, ctx) => {
// You can access the session from the context object.
if (ctx.context.session) {
console.log("User update initiated by:", ctx.context.session.userId);
}
return { data };
},
},
},
},
});与标准钩子类似,数据库钩子也提供一个 ctx 对象,它提供各种有用的属性。有关更多信息,请参阅钩子文档。
插件架构
插件可以在数据库中定义自己的表来存储附加数据。它们还可以向核心表添加列来存储附加数据。例如,双因素认证插件向 user 表添加以下列:
twoFactorEnabled:用户是否启用了双因素认证。twoFactorSecret:用于生成 TOTP 代码的密钥。twoFactorBackupCodes:用于账户恢复的加密备份代码。
要向数据库添加新表和列,您有两种选项:
CLI:使用 migrate 或 generate 命令。这些命令将扫描您的数据库并指导您添加任何缺失的表或列。
手动方法:按照插件文档中的说明手动添加表和列。
两种方法均可确保您的数据库架构与插件的要求保持同步。