数据库
了解数据库适配器、迁移、使用 Redis 的二级存储、核心架构(用户、会话、账户、验证)、自定义表、扩展架构、ID 生成、数据库钩子以及插件架构。
适配器
Better Auth 连接数据库以存储数据。数据库将用于存储用户、会话等数据。插件也可以定义自己的数据库表以存储数据。
你可以通过在数据库选项中传入支持的数据库实例,将数据库连接传递给 Better Auth。你可以在 其他关系型数据库 文档中了解更多支持的数据库适配器。
Better Auth also works without any database. For more details, see Stateless Session Management.
命令行工具(CLI)
Better Auth 配备了 CLI 工具,用于管理数据库迁移和生成架构。
运行迁移
CLI 会检查你的数据库,并提示你添加缺失的表或用新列更新现有表。这仅支持内置的 Kysely 适配器。对于其他适配器,可以使用 generate 命令生成架构,并通过你的 ORM 处理迁移。
npx auth@latest migrateFor PostgreSQL users: The migrate command supports non-default schemas. It automatically detects your search_path configuration and creates tables in the correct schema. See PostgreSQL adapter for details.
生成架构
Better Auth 还提供了 generate 命令,用于生成 Better Auth 所需的架构。若你使用的是 Prisma 或 Drizzle 等数据库适配器,此命令会为你的 ORM 生成正确的架构。如果使用内置的 Kysely 适配器,它将生成一个 SQL 文件,可直接运行于你的数据库。
npx auth@latest generate更多 CLI 信息,请参阅 CLI 文档。
如果你更喜欢手动添加表也可以。Better Auth 所需的核心架构如下所述,插件文档中可以找到插件所需的额外架构。
编程式迁移
在无法使用 CLI 的环境中(例如 Cloudflare Workers,无服务器函数),你可以使用 better-auth/db/migration 中的 getMigrations 编程运行迁移。
import { getMigrations } from "better-auth/db/migration";
import { auth } from "./auth";
const { toBeCreated, toBeAdded, runMigrations } = await getMigrations(auth.options);
await runMigrations();getMigrations only works with the built-in Kysely adapter (SQLite/D1, PostgreSQL, MySQL, MSSQL). It does not work with Prisma or Drizzle ORM adapters — use CLI migrations with those ORMs instead.
二级存储
Better Auth 的辅助存储允许您使用键值存储来管理会话数据、验证记录、限流计数器以及其他短暂的认证数据。当您希望将高强度记录的存储卸载到高性能存储甚至内存时,这会非常有用。
实现
要使用二级存储,实现 SecondaryStorage 接口:
interface SecondaryStorage {
get: (key: string) => Promise<unknown>;
set: (key: string, value: string, ttl?: number) => Promise<void>;
delete: (key: string) => Promise<void>;
}然后,将你的实现传递给 betterAuth 函数:
import { betterAuth } from "better-auth";
betterAuth({
// ... 其他选项
secondaryStorage: {
// 你的实现
},
});Redis 存储
Better Auth 提供官方的 Redis 存储包,基于 ioredis:
npm install @better-auth/redis-storage ioredis
# 或者
pnpm add @better-auth/redis-storage ioredis使用示例:
import { betterAuth } from "better-auth";
import { Redis } from "ioredis";
import { redisStorage } from "@better-auth/redis-storage";
const redis = new Redis({
host: "localhost",
port: 6379,
});
export const auth = betterAuth({
// ... 其他选项
secondaryStorage: redisStorage({
client: redis,
keyPrefix: "better-auth:", // 可选,默认为 "better-auth:"
}),
});Redis 存储支持 ioredis 的所有连接模式,包括单机、集群和哨兵配置。
手动实现示例:
如果你想自己实现 Redis 二级存储,下面是一个基础示例:
import { createClient } from "redis";
import { betterAuth } from "better-auth";
const redis = createClient();
await redis.connect();
export const auth = betterAuth({
// ... 其他选项
secondaryStorage: {
get: async (key) => {
return await redis.get(key);
},
set: async (key, value, ttl) => {
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 存储会话数据和限速计数器。你还可以为键名添加前缀。
示例:Upstash Redis 实现
以下是使用 Upstash Redis 的示例。首先,安装 Upstash Redis 客户端:
npm install @upstash/redis然后,创建一个新的 Redis 客户端:
// src/lib/redis/index.ts
import { Redis } from "@upstash/redis";
export const redis = Redis.fromEnv();别忘了设置 UPSTASH_REDIS_REST_URL 和 UPSTASH_REDIS_REST_TOKEN 环境变量。有关如何使用 Upstash Redis 结合 Node.js 的更多信息,请参见 Upstash 文档。
之后,我们可以创建一个二级存储的实现:
// src/lib/auth/adapters/redis-secondary-storage.ts
import { SecondaryStorage } from "better-auth";
import { redis } from "~/lib/redis";
export const redisSecondaryStorage: SecondaryStorage = {
async get(key: string) {
try {
const value = await redis.get(key);
// 处理 Redis 返回的不同类型
if (value === null || value === undefined) {
return null;
}
// 如果已经是字符串,则直接返回
if (typeof value === "string") {
return value;
}
// 如果是对象,则进行字符串化
if (typeof value === "object") {
return JSON.stringify(value);
}
// 其他类型转换为字符串返回
return String(value);
} catch (error) {
console.error("Redis get 错误:", error);
return null;
}
},
async set(key: string, value: string, ttl?: number) {
try {
// 确保值为字符串
const stringValue =
typeof value === "string" ? value : JSON.stringify(value);
if (ttl) {
// 带 TTL(秒)设置
await redis.set(key, stringValue, { ex: ttl });
} else {
// 不带 TTL 设置
await redis.set(key, stringValue);
}
} catch (error) {
console.error("Redis set 错误:", error);
throw error;
}
},
async delete(key: string) {
try {
await redis.del(key);
} catch (error) {
console.error("Redis delete 错误:", error);
throw error;
}
},
};最后,我们可以将该实现传递给 betterAuth 函数。
import { betterAuth } from "better-auth";
import { redisSecondaryStorage } from "~/lib/auth/adapters/redis-secondary-storage";
export const auth = betterAuth({
// ...其他选项
secondaryStorage: redisSecondaryStorage,
});核心架构
Better Auth 需要数据库中存在以下表。下面是 typescript 格式的类型定义。你可以在数据库中使用对应类型。
用户表
表名:user
会话表
表名:session
账户表
表名:account
验证表
表名:verification
自定义表
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: ["user", "admin"],
required: false,
defaultValue: "user",
input: false, // 不允许用户设置角色
},
lang: {
type: "string",
required: false,
defaultValue: "en",
},
},
},
});这使您可以在应用程序逻辑中访问这些额外字段。
// 注册期间
const res = await auth.api.signUpEmail({
body: {
email: 'test@example.com',
password: 'password',
name: 'John Doe',
lang: 'fr',
},
});
// 用户对象
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。
您可以通过 advanced.database.generateId 选项自定义 ID 生成行为。
选项 1:使用数据库生成 ID
将 generateId 设置为 false,让数据库处理所有 ID 生成(除了 generateId 为 "serial" 和某些 uuid 的情况):
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: db,
advanced: {
database: {
generateId: false, // 如果您想要自动递增的数字 ID,请使用 "serial"
},
},
});选项 2:自定义 ID 生成函数
使用函数生成 ID。返回 false 或 undefined 告诉数据库为特定模型生成 ID:
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: db,
advanced: {
database: {
generateId: (options) => {
// 让数据库为特定模型生成 ID
if (options.model === "user" || options.model === "users") {
return false; // 数据库生成 ID
}
// 其他表生成 UUID
return crypto.randomUUID();
},
},
},
});重要:当 generateId 函数返回 false 或 undefined 时,对应模型的 ID 由数据库生成。直接将 generateId: false(而非函数)设置为 false 会禁用所有表的 ID 生成。
选项 3:统一的 ID 生成器
为所有表生成相同类型的 ID:
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: db,
advanced: {
database: {
generateId: () => crypto.randomUUID(),
},
},
});数字 ID
如果您更喜欢自动递增的数字 ID,请将 advanced.database.generateId 设置为 "serial"。
在这种情况下,Better Auth 不会生成 ID,而是假设数据库会自动生成数字 ID。
启用后,Better Auth CLI 将生成或迁移数据库的 id 字段为具有自动递增属性的数字类型。
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: db,
advanced: {
database: {
generateId: "serial",
},
},
});Better Auth 仍将数据库中的 id 字段视为字符串类型,并在检索或插入数据时自动转换为数字类型。
从 Better Auth 获取的 id 可能是表示数字的字符串形式,这是正常的。您传递给 Better Auth 的 id 也应该是字符串。
UUID
如果您希望使用 UUID 类型的 id 字段,请将 advanced.database.generateId 设置为 "uuid"。
默认情况下,Better Auth 为所有表生成 UUID,除了 PostgreSQL 适配器(它支持数据库生成的 UUID)。
启用后,Better Auth CLI 将生成或迁移您的数据库架构,并为 id 字段使用 UUID 类型。
如果数据库不支持 UUID 类型,它会回退到纯字符串类型。
混合 ID 类型
如果您需要在不同表中使用不同的 ID 类型(例如,用户表使用整数,其他表使用 UUID 字符串),请使用 generateId 回调函数方法。
import { betterAuth } from "better-auth";
import { db } from "./db";
export const auth = betterAuth({
database: db,
user: {
modelName: "users", // PostgreSQL: id 是 serial 主键
},
session: {
modelName: "session", // PostgreSQL: id 是 text 主键
},
advanced: {
database: {
// 不要设置 useNumberId —— 这个选项是全局的,会影响所有表
generateId: (options) => {
if (options.model === "user" || options.model === "users") {
return false; // 让 PostgreSQL serial 生成 ID
}
return crypto.randomUUID(); // session、account、verification 生成 UUID
},
},
},
});此配置允许您:
- 使用数据库自动递增(serial、auto_increment 等)作为用户表
- 为所有其他表(session、account、verification)生成 UUID
- 与使用不同 ID 类型的现有架构保持兼容
使用场景:这在从另一个身份验证提供程序(例如 Clerk)迁移时特别有用,其中您的用户 ID 可能是整数,而新表使用 UUID 字符串。
数据库钩子
数据库钩子允许您定义在 Better Auth 核心数据库操作生命周期期间运行的自定义逻辑。您可以为以下模型创建钩子:user、session 和 account。
支持额外字段,但这些字段的完整类型推断尚未支持,未来将会改进。
您可以定义两种类型的钩子:
1. Before 钩子
- 目的:在创建、更新或删除相应实体(用户、会话或账户)之前调用此钩子。
- 行为:如果钩子返回
false,操作将被中止。如果它返回数据对象,它将替换原始有效载荷。
2. After 钩子
- 目的:在相应实体创建或更新后调用此钩子。
- 行为:可以执行额外操作或在实体成功创建或更新后进行修改。
示例用法
import { betterAuth } from "better-auth";
export const auth = betterAuth({
databaseHooks: {
user: {
create: {
before: async (user, ctx) => {
// 在创建之前修改用户对象
return {
data: {
// 注意:返回 Better Auth 命名字段,而不是原始数据库字段名
...user,
firstName: user.name.split(" ")[0],
lastName: user.name.split(" ")[1],
},
};
},
after: async (user) => {
// 执行额外操作,例如创建 Stripe 客户
},
},
delete: {
before: async (user, ctx) => {
console.log(`User ${user.email} is being deleted`);
if (user.email.includes("admin")) {
return false; // 中止删除
}
return true; // 允许删除
},
after: async (user) => {
console.log(`User ${user.email} has been deleted`);
},
},
},
session: {
delete: {
before: async (session, ctx) => {
console.log(`Session ${session.token} is being deleted`);
if (session.userId === "admin-user-id") {
return false; // 中止删除
}
return true; // 允许删除
},
after: async (session) => {
console.log(`Session ${session.token} has been deleted`);
},
},
},
},
});抛出错误
如果您想要在数据库钩子中停止执行,可以抛出使用 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) {
// 您的自定义条件
// 抛出 API 错误
throw new APIError("BAD_REQUEST", {
message: "Users must agree to the terms of service before registering.",
});
}
return {
data: user,
};
},
},
},
},
});使用上下文对象
上下文对象(ctx)作为第二个参数传递给钩子,并包含有用的信息。对于 update 钩子,ctx 包含当前的 session,允许访问经过身份验证的用户的详细信息。
import { betterAuth } from "better-auth";
export const auth = betterAuth({
databaseHooks: {
user: {
update: {
before: async (data, ctx) => {
// 通过 ctx 访问会话
if (ctx.context.session) {
console.log("User update initiated by:", ctx.context.session.userId);
}
return { data };
},
},
},
},
});数据库钩子与标准钩子一样,也提供丰富的 ctx 对象属性。有关详细信息,请参阅 hooks 文档。
插件 Schema
插件可以在数据库中定义自己的表来存储更多数据,也可以向核心表中添加字段以存储额外信息。例如,双因素认证插件会向 user 表添加以下字段:
twoFactorEnabled:该用户是否启用了双因素认证。twoFactorSecret:用于生成 TOTP 码的密钥。twoFactorBackupCodes:用于账户恢复的加密备份码。
向数据库添加表和列有两种选择:
- CLI:使用迁移或生成命令。该命令会扫描数据库,并引导你添加缺失的表或列。
- 手动方法:按照插件文档手动添加表和列。
实验性关联查询
从 Better Auth 1.4 开始,实验性的数据库关联查询功能已添加。
这允许 Better Auth 在单个请求中执行多个数据库查询,减少数据库往返次数。
超过 50 个端点支持关联查询,并且我们正在持续添加更多。
在内部,我们的适配器系统原生支持关联查询。即使你没有启用实验性关联查询, 它仍然会回退到执行多个查询并合并结果。
要启用关联查询,请在你的认证配置中添加以下内容:
import { betterAuth } from "better-auth";
export const auth = betterAuth({
experimental: { joins: true }
});Better Auth 1.4 CLI 会自动为 DrizzleORM 和 PrismaORM 生成关联关系,因此如果你尚未添加它们,请运行迁移或生成命令以更新到最新的必需架构。
我们强烈建议阅读对应适配器的实验性关联查询文档: