数据库

适配器

Better Auth 需要数据库连接来存储数据。它内置了一个名为 Kysely 的查询构建器来管理和查询数据库。数据库将用于存储用户、会话等数据。插件也可以定义自己的数据库表来存储数据。

你可以通过在数据库选项中传入支持的数据库实例、方言实例或 Kysely 实例来为 Better Auth 提供数据库连接。

你可以在其他关系型数据库文档中了解更多关于支持的 Kysely 方言。如果你使用的是 ORM,你可以在文档侧边栏的同一类别中找到我们支持的 ORM 适配器。

命令行工具

Better Auth 提供了一个命令行工具来管理数据库迁移和生成架构。

运行迁移

命令行工具会检查你的数据库并提示你添加缺失的表或使用新列更新现有表。这仅支持内置的 Kysely 适配器。对于其他适配器,你可以使用 generate 命令来创建架构,并通过你的 ORM 处理迁移。

npx @better-auth/cli migrate

生成数据库表

Better Auth 还提供了 generate 命令来生成数据库表。generate 命令会创建 Better Auth 所需的架构。如果你使用的是 Prisma 或 Drizzle 等数据库适配器,此命令将为你的 ORM 生成相应的架构。如果你使用内置的 Kysely 适配器,它将生成一个可以直接在数据库中运行的 SQL 文件。

npx @better-auth/cli generate

有关命令行工具的更多信息,请参阅命令行工具文档。

如果你更喜欢手动添加表,也可以这样做。下面描述了 Better Auth 所需的核心架构,你可以在插件文档中找到插件所需的其他架构。

二级存储

Better Auth 中的二级存储允许你使用键值存储来管理会话数据、速率限制计数器等。当你想要将这些密集记录的存储卸载到高性能存储甚至 RAM 时,这会很有用。

实现

要使用二级存储,请实现 SecondaryStorage 接口:

interface SecondaryStorage {
  get: (key: string) => Promise<string | null>;
  set: (key: string, value: string, ttl?: number) => Promise<void>;
  delete: (key: string) => Promise<void>;
}

然后,将你的实现提供给 betterAuth 函数:

betterAuth({
  // ... 其他选项
  secondaryStorage: {
    // 在这里实现你的存储
  },
});

示例:Redis 实现

这是一个使用 Redis 的基本示例:

import { createClient } from "redis";
import { betterAuth } from "better-auth";
 
const redis = createClient();
await redis.connect();
 
export const auth = betterAuth({
    // ... 其他选项
    secondaryStorage: {
        get: async (key) => {
            const value = await redis.get(key);
            return value ? value : null;
        },
        set: async (key, value, ttl) => {
            if (ttl) await redis.set(key, value, { EX: ttl });
            // 或者对于 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 NameTypeKeyDescription
idstring用户的唯一标识符
namestring-用户选择的显示名称
emailstring-用户的电子邮件地址,用于通信和登录
emailVerifiedboolean-用户的电子邮件是否已验证
imagestring用户的头像 URL
createdAtDate-用户账户创建的时间戳
updatedAtDate-用户信息最后更新的时间戳

会话表

表名:session

Field NameTypeKeyDescription
idstring会话的唯一标识符
userIdstring用户的 ID
tokenstring-唯一的会话令牌
expiresAtDate-会话过期的时间
ipAddressstring设备的 IP 地址
userAgentstring设备的用户代理信息
createdAtDate-会话创建的时间戳
updatedAtDate-会话更新的时间戳

账户表

表名:account

Field NameTypeKeyDescription
idstring账户的唯一标识符
userIdstring用户的 ID
accountIdstring-SSO 提供的账户 ID,或对于凭证账户等于 userId
providerIdstring-提供者的 ID
accessTokenstring账户的访问令牌,由提供者返回
refreshTokenstring账户的刷新令牌,由提供者返回
accessTokenExpiresAtDate访问令牌过期的时间
refreshTokenExpiresAtDate刷新令牌过期的时间
scopestring账户的作用域,由提供者返回
idTokenstring提供者返回的 ID 令牌
passwordstring账户的密码,主要用于邮箱和密码认证
createdAtDate-账户创建的时间戳
updatedAtDate-账户更新的时间戳

验证表

表名:verification

Field NameTypeKeyDescription
idstring验证的唯一标识符
identifierstring-验证请求的标识符
valuestring-要验证的值
expiresAtDate-验证请求过期的时间
createdAtDate-验证请求创建的时间戳
updatedAtDate-验证请求更新的时间戳

自定义表

Better Auth 允许你自定义核心架构的表名和列名。你还可以通过向用户和会话表添加额外字段来扩展核心架构。

自定义表名

你可以通过在认证配置中使用 modelNamefields 属性来自定义核心架构的表名和列名:

auth.ts
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 属性:

auth.ts
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 提供了一种类型安全的方式来扩展 usersession 架构。你可以在认证配置中添加自定义字段,命令行工具将自动更新数据库架构。这些额外字段将在 useSessionsignUp.email 和其他处理用户或会话对象的端点中正确推断。

要添加自定义字段,请在认证配置的 usersession 对象中使用 additionalFields 属性。additionalFields 对象使用字段名作为键,每个值都是一个包含以下内容的 FieldAttributes 对象:

  • type:字段的数据类型(例如:"string"、"number"、"boolean")。
  • required:表示字段是否必填的布尔值。
  • defaultValue:字段的默认值(注意:这只适用于 JavaScript 层;在数据库中,该字段将是可选的)。
  • input:这决定了在创建新记录时是否可以提供值(默认:true)。如果有一些额外字段,比如 role,在注册时不应该由用户提供,你可以将其设置为 false

以下是如何使用额外字段扩展用户架构的示例:

auth.ts
import { betterAuth } from "better-auth";
 
export const auth = betterAuth({
  user: {
    additionalFields: {
      role: {
        type: "string",
        required: false,
        defaultValue: "user",
        input: false, // 不允许用户设置角色
      },
      lang: {
        type: "string",
        required: false,
        defaultValue: "en",
      },
    },
  },
});

现在你可以在应用程序逻辑中访问这些额外字段。

// 在注册时
const res = await auth.api.signUpEmail({
  email: "[email protected]",
  password: "password",
  name: "John Doe",
  lang: "zh",
});
 
// 用户对象
res.user.role; // > "admin"
res.user.lang; // > "zh"

查看TypeScript文档以了解更多关于如何在客户端推断额外字段的信息。

如果你使用社交/OAuth 提供者,你可能想要提供 mapProfileToUser 来将配置文件数据映射到用户对象。这样,你就可以从提供者的配置文件中填充额外字段。

示例:将配置文件映射到用户的 firstNamelastName

auth.ts
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

auth.ts
import { betterAuth } from "better-auth";
import { db } from "./db";
 
export const auth = betterAuth({
  database: {
    db: db,
  },
  advanced: {
    database: {
      generateId: false,
    },
  },
});

数据库钩子

数据库钩子允许你定义在 Better Auth 核心数据库操作生命周期中执行的自定义逻辑。你可以为以下模型创建钩子:usersessionaccount

你可以定义两种类型的钩子:

1. 前置钩子

  • 目的:此钩子在相应实体(用户、会话或账户)创建或更新之前调用。
  • 行为:如果钩子返回 false,操作将被中止。如果返回数据对象,它将替换原始负载。

2. 后置钩子

  • 目的:此钩子在相应实体创建或更新之后调用。
  • 行为:你可以在实体成功创建或更新后执行额外的操作或修改。

使用示例

auth.ts
import { betterAuth } from "better-auth";
 
export const auth = betterAuth({
  databaseHooks: {
    user: {
      create: {
        before: async (user, ctx) => {
          // 在创建用户之前修改用户对象
          return {
            data: {
              ...user,
              firstName: user.name.split(" ")[0],
              lastName: user.name.split(" ")[1],
            },
          };
        },
        after: async (user) => {
          // 执行额外操作,比如创建 stripe 客户
        },
      },
    },
  },
});

抛出错误

如果你想阻止数据库钩子继续执行,你可以使用从 better-auth/api 导入的 APIError 类抛出错误。

auth.ts
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: "用户必须在注册前同意服务条款。",
            });
          }
          return {
            data: user,
          };
        },
      },
    },
  },
});

与标准钩子一样,数据库钩子也提供了一个 ctx 对象,它提供了各种有用的属性。在钩子文档中了解更多信息。

插件架构

插件可以在数据库中定义自己的表来存储额外数据。它们还可以向核心表添加列来存储额外数据。例如,双因素认证插件向 user 表添加了以下列:

  • twoFactorEnabled:用户是否启用了双因素认证。
  • twoFactorSecret:用于生成 TOTP 码的密钥。
  • twoFactorBackupCodes:用于账户恢复的加密备份码。

要向数据库添加新表和列,你有两个选项:

命令行工具:使用 migrate 或 generate 命令。这些命令将扫描你的数据库并指导你添加任何缺失的表或列。 手动方法:按照插件文档中的说明手动添加表和列。

这两种方法都确保你的数据库架构与插件的要求保持同步。

On this page