高级功能

高级 API 密钥功能,包括会话、多配置、组织密钥、存储模式等。

来自 API 密钥的会话

每当调用 Better Auth 中带有有效 API 密钥的接口时,可以通过启用 enableSessionForAPIKeys 选项,自动创建一个模拟会话以代表用户。

这通常不推荐,因为如果使用不当可能导致安全问题。泄露的 API 密钥可以被用来冒充用户。

仅限用户拥有的密钥:会话模拟仅适用于用户拥有的 API 密钥(即 references: "user")。组织拥有的密钥无法模拟用户会话。

速率限制说明:启用 enableSessionForAPIKeys 后,每次请求都会验证一次 API 密钥,并相应应用速率限制。 如果手动验证 API 密钥后再单独获取会话,这两次操作都会增加速率限制计数。使用 enableSessionForAPIKeys 可以避免此类重复计数。

import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      enableSessionForAPIKeys: true,
    }),
  ],
});
import { auth } from "@/lib/auth"

const session = await auth.api.getSession({
      headers: new Headers({
            'x-api-key': apiKey,
      }),
});

默认的请求头字段是 x-api-key,但可在插件选项中通过设置 apiKeyHeaders 自定义。

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      apiKeyHeaders: ["x-api-key", "xyz-api-key"], // 或者直接传一个字符串,例如 "x-api-key"
    }),
  ],
});

或可传入 customAPIKeyGetter 函数(接收 HookEndpointContext),由你返回请求中的 API 密钥,若请求无效则返回 null

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      customAPIKeyGetter: (ctx) => {
        const has = ctx.request.headers.has("x-api-key");
        if (!has) return null;
        return ctx.request.headers.get("x-api-key");
      },
    }),
  ],
});

多配置支持

你可以定义多个不同设置的 API 密钥配置。每个配置通过唯一的 configId 标识,可以定制前缀、速率限制、权限等。

这在需要为不同用途定义不同类型的 API 密钥时非常有用,例如:

  • 公钥与私钥
  • 只读与读写密钥
  • 不同层级的速率限制

配置示例

将配置对象数组传递给 apiKey 插件:

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey([
      {
        configId: "public",
        defaultPrefix: "pk_",
        rateLimit: {
          enabled: true,
          maxRequests: 100,
          timeWindow: 1000 * 60 * 60, // 1 小时
        },
      },
      {
        configId: "secret",
        defaultPrefix: "sk_",
        enableMetadata: true,
        rateLimit: {
          enabled: true,
          maxRequests: 1000,
          timeWindow: 1000 * 60 * 60, // 1 小时
        },
      },
    ]),
  ],
});

创建指定配置的密钥

创建 API 密钥时,通过 configId 参数指定使用哪个配置:

create-api-key.ts
import { auth } from "@/lib/auth"

// 创建公开密钥
const publicKey = await auth.api.createApiKey({
  body: {
    configId: "public",
    userId: user.id,
  },
});
// 返回示例: pk_...

// 创建私密密钥
const secretKey = await auth.api.createApiKey({
  body: {
    configId: "secret",
    userId: user.id,
    metadata: { plan: "premium" },
  },
});
// 返回示例: sk_...

getupdatedelete 必须传入与该密钥创建时相同的 configIdverify 会解析密钥自身的配置,因此只有当某个配置在存储或哈希方面与默认配置不同的时候,才需要传入 configId

在 API 密钥操作中使用 configId

所有 API 密钥操作都支持 configId 参数,用于指定查询使用哪套配置,特别是在配置使用不同存储后端(如数据库与 Redis)时非常重要:

api-key-operations.ts
// 指定配置获取 API 密钥
const key = await auth.api.getApiKey({
  query: { 
    id: keyId,
    configId: "secret" 
  },
  headers,
});

// 指定配置更新 API 密钥
await auth.api.updateApiKey({
  body: {
    keyId: keyId,
    configId: "secret",
    name: "Updated Name",
  },
});

// 指定配置删除 API 密钥
await auth.api.deleteApiKey({
  body: {
    keyId: keyId,
    configId: "secret",
  },
  headers,
});

// 指定配置验证 API 密钥
const result = await auth.api.verifyApiKey({
  body: {
    key: apiKeyValue,
    configId: "secret",
  },
});

按配置筛选密钥

列出 API 密钥时,可通过 configId 过滤:

list-api-keys.ts
// 仅列出公开密钥
const publicKeys = await authClient.apiKey.list({
  query: { configId: "public" }
});

// 仅列出私密密钥
const secretKeys = await authClient.apiKey.list({
  query: { configId: "secret" }
});

全局选项

也可以传入第二个参数传递全局选项(比如 schema):

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey(
      [
        { configId: "public", defaultPrefix: "pk_" },
        { configId: "secret", defaultPrefix: "sk_" },
      ],
      {
        schema: {
          // 自定义 schema 选项
        },
      }
    ),
  ],
});

组织拥有的 API 密钥

默认情况下,API 密钥由用户拥有。你也可以配置 API 密钥由组织拥有,适合团队应用场景,API 密钥在组织成员间共享。

配置示例

在配置中设置 references: "organization"

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey([
      {
        configId: "user-keys",
        defaultPrefix: "user_",
        references: "user", // 默认,用户拥有
      },
      {
        configId: "org-keys",
        defaultPrefix: "org_",
        references: "organization", // 组织拥有
      },
    ]),
  ],
});

创建组织拥有的密钥

创建组织拥有的 API 密钥时,请将 organizationId 与用户上下文一起传入。该用户必须是组织成员,并且有权限创建 API 密钥。该用户会从会话(请求 headers)中获取;如果是纯服务端调用且不传 headers,则改为在 body 中传入 userId

create-org-api-key.ts
import { auth } from "@/lib/auth"

const orgKey = await auth.api.createApiKey({
  body: {
    configId: "org-keys",
    organizationId: "org_123", // 拥有该密钥的组织
    userId: "user_123", // 允许创建密钥的成员;如果传入会话 headers,则省略
  },
});

会话模拟限制enableSessionForAPIKeys 仅适用于用户拥有的 API 密钥。组织拥有的密钥由于没有单一用户关联,无法模拟用户会话。

访问控制与权限

组织拥有的 API 密钥使用组织插件的基于角色的访问控制系统。管理组织 API 密钥的用户必须:

  1. 是该组织成员
  2. 拥有执行操作所需的 apiKey 权限

API 密钥权限

API 密钥插件使用以下权限:

操作权限描述
创建apiKey: ["create"]创建新的组织 API 密钥
读取/列表apiKey: ["read"]查看和列出组织 API 密钥
更新apiKey: ["update"]修改组织 API 密钥
删除apiKey: ["delete"]删除组织 API 密钥

配置组织角色权限

默认情况下,组织拥有者拥有所有 API 密钥操作的完全访问权限。对于其他角色(如 adminmember),需在组织插件配置中显式赋予 apiKey 权限。

示例配置角色及权限如下:

auth.ts
import { betterAuth } from "better-auth"
import { organization } from "better-auth/plugins"
import { apiKey } from "@better-auth/api-key"
import { createAccessControl } from "better-auth/plugins/access"

// 定义包含 apiKey 权限的访问控制语句
const statements = {
  // ... 其他语句
  // 添加 apiKey 权限
  apiKey: ["create", "read", "update", "delete"], 
} as const;

const ac = createAccessControl(statements);

// 定义具有特定 apiKey 权限的角色
const adminRole = ac.newRole({
  // ... 其他权限
  // 管理员可管理 API 密钥
  apiKey: ["create", "read", "update", "delete"], 
});

const memberRole = ac.newRole({
  // ... 其他权限
  // 成员仅可查看 API 密钥
  apiKey: ["read"], 
});

export const auth = betterAuth({
  plugins: [
    organization({
      ac,
      roles: {
        admin: adminRole,
        member: memberRole,
      },
      async sendInvitationEmail() {},
    }),
    apiKey([
      {
        configId: "org-keys",
        defaultPrefix: "org_",
        references: "organization",
      },
    ]),
  ],
});

拥有者访问权限:组织拥有者(creatorRole,默认 "owner")自动具备所有 API 密钥操作的完全访问权限,无需显式配置权限。

权限示例

// 管理员可创建、读取、更新、删除组织 API 密钥
const key = await auth.api.createApiKey({
  body: { configId: "org-keys", organizationId: "org_123" },
  headers: adminHeaders,
});

// 仅有 "read" 权限的成员可列出密钥
const keys = await client.apiKey.list(
  { query: { organizationId: "org_123" } },
  { headers: memberHeaders },
);

// 仅有 "read" 权限的成员尝试创建密钥将报错
const result = await client.apiKey.create(
  { configId: "org-keys", organizationId: "org_123" },
  { headers: memberHeaders },
);
// 错误: INSUFFICIENT_API_KEY_PERMISSIONS

错误码说明

访问被拒时,返回以下错误码:

  • USER_NOT_MEMBER_OF_ORGANIZATION: 用户不是该组织成员
  • INSUFFICIENT_API_KEY_PERMISSIONS: 用户没有执行该操作所需的 apiKey 权限

API 密钥对象结构

API 密钥包含 configId 表示所属配置,referenceId 表示拥有者 ID:

type ApiKey = {
  id: string;
  configId: string;    // 所属配置
  referenceId: string; // 拥有者 ID(基于配置为 userId 或 organizationId)
  // ... 其他字段
};

拥有者类型通过配置中 references 字段判断:

const apiKey = await auth.api.getApiKey({
  query: { id: keyId },
  headers,
});

// 依据配置中 `references` 判断拥有者类型
// 对于 org-keys 配置(references: "organization"):
console.log(`密钥拥有者:${apiKey.referenceId}`);

存储模式

API Key 插件支持多种存储模式,满足不同场景灵活管理 API 密钥。

存储模式选项

"database"(默认)

仅将 API 密钥存储在数据库中。这是默认模式,无需额外配置。

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      storage: "database", // 默认,可省略
    }),
  ],
});

"secondary-storage"

只存储于二级存储(如 Redis),不回退数据库。适合所有密钥都迁移到二级存储的高性能场景。

auth.ts
import { createClient } from "redis";
import { betterAuth } from "better-auth";
import { apiKey } from "@better-auth/api-key";

const redis = createClient();
await redis.connect();

export const auth = betterAuth({
  secondaryStorage: {
    get: async (key) => 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),
  },
  plugins: [
    apiKey({
      storage: "secondary-storage",
    }),
  ],
});

带回退的二级存储

先查询二级存储,若未命中则回退数据库查询。

读取行为:

  • 先检查二级存储
  • 如果未命中,则查询数据库
  • 自动填充二级存储(缓存预热),当从数据库回退时
  • 确保频繁访问的密钥长期保留在缓存中

写入行为:

  • 同时写入 数据库二级存储
  • 确保两者之间的一致性
auth.ts
import { betterAuth } from "better-auth"
import { createClient } from "redis";

const redis = createClient();
await redis.connect();

export const auth = betterAuth({
  secondaryStorage: {
    get: async (key) => 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),
  },
  plugins: [
    apiKey({
      storage: "secondary-storage",
      fallbackToDatabase: true,
    }),
  ],
});

自定义存储方法

可以为 API 密钥单独覆盖全局的 secondaryStorage,提供自定义存储逻辑:

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      storage: "secondary-storage",
      customStorage: {
        get: async (key) => {
          // API 密钥的自定义获取逻辑
          return await customStorage.get(key);
        },
        set: async (key, value, ttl) => {
          // API 密钥的自定义存储逻辑
          await customStorage.set(key, value, ttl);
        },
        delete: async (key) => {
          // API 密钥的自定义删除逻辑
          await customStorage.delete(key);
        },
      },
    }),
  ],
});

速率限制

每个 API 密钥都可以有自己的速率限制设置。每次验证 API 密钥时,都会应用内置的速率限制,包括:

  • 通过 /api-key/verify 端点验证 API 密钥时
  • 使用 API 密钥创建会话时(如果启用了 enableSessionForAPIKeys),速率限制将应用于该 API 密钥所使用的所有端点

对于其他不使用 API 密钥的端点/方法,请使用 Better Auth 的 内置速率限制

双重速率限制计数增加:如果你手动使用 verifyApiKey() 验证 API 密钥,然后又使用同一个 API 密钥请求头通过 getSession() 获取会话,这两个操作都会增加速率限制计数器,导致一次请求被增加两次。为避免这种情况:

  • 使用 enableSessionForAPIKeys: true,让 Better Auth 自动处理会话创建(推荐)
  • 或者只验证 API 密钥一次,并复用已验证的结果,而不是分别调用这两个方法

你可以在 API Key 插件选项 中查看默认的速率限制配置。

默认配置示例:

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      rateLimit: {
        enabled: true,
        timeWindow: 1000 * 60 * 60 * 24, // 1 天
        maxRequests: 10, // 每天最多 10 个请求
      },
    }),
  ],
});

每个 API 密钥都可以在创建时自定义速率限制选项。

速率限制选项只能在服务端的 auth 实例中自定义。

create-api-key.ts
import { auth } from "@/lib/auth"

const apiKey = await auth.api.createApiKey({
  body: {
    rateLimitEnabled: true,
    rateLimitTimeWindow: 1000 * 60 * 60 * 24, // 1 天
    rateLimitMax: 10, // 每天最多 10 个请求
  },
  headers: await headers() // 包含用户会话令牌的请求头
});

工作原理

速率限制使用滑动窗口方式:

  1. 第一次请求:当 API 密钥首次使用时(没有 lastRequest),请求被允许,且 requestCount 被设为 1。

  2. 窗口内请求:在 timeWindow 内的后续请求会增加 requestCount。当达到 rateLimitMax 时,请求会被拒绝并返回 RATE_LIMITED 错误。

  3. 窗口重置:如果距离上次请求的时间超过 timeWindow,窗口将重置:requestCount 重置为 1,并更新 lastRequest 时间。

  4. 超限提示:当超过限制时,响应会包含 tryAgainIn 字段(毫秒),表示距离下次重置还需多久。

禁用速率限制

  • 全局:在插件选项中设置 rateLimit.enabled: false
  • 单个密钥:在创建或更新 API 密钥时设置 rateLimitEnabled: false
  • 空值:如果 rateLimitTimeWindowrateLimitMaxnull,则该密钥的速率限制实际上被禁用

在禁用时,请求仍然会被允许,但 lastRequest 仍会更新用于追踪。

剩余额度、补充与过期

剩余额度表示 API 密钥仍可发出的请求数量。 补充间隔表示在满足条件时,剩余额度在被补充前必须经过的毫秒数。 过期时间表示 API 密钥何时过期。

工作原理

剩余额度

每次使用 API 密钥时,都会更新 remaining 额度。 如果 remainingnull,则可无限使用。 否则,remaining 会减 1。 如果它降到 0,API 密钥将被禁用并移除。

补充间隔与补充数量

创建 API 密钥时,refillIntervalrefillAmount 默认设置为 null。 这意味着 API 密钥不会自动补充。 不过,如果同时设置了 refillIntervalrefillAmount,那么每次使用 API 密钥时:

  • 系统会检查距离上次补充(或创建后的首次补充)是否超过 refillInterval
  • 如果间隔已过,remaining 数量会重置为 refillAmount(不是递增)
  • lastRefillAt 时间戳会更新为当前时间

过期时间

默认情况下,创建时 expiresAtnull,表示永不过期。 如果设置了 expiresIn,密钥将在指定时间后过期。

自定义密钥生成与验证

你可以直接在插件选项中自定义密钥生成和验证逻辑。

示例:

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      customKeyGenerator: (options: {
        length: number;
        prefix: string | undefined;
      }) => {
        const apiKey = mySuperSecretApiKeyGenerator(
          options.length,
          options.prefix
        );
        return apiKey;
      },
      customAPIKeyValidator: async ({ ctx, key }) => {
        const res = await keyService.verify(key)
        return res.valid
      },
    }),
  ],
});

如果你没有使用 customKeyGenerator 提供的 length 属性,必须defaultKeyLength 属性设置为生成密钥的长度。

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      customKeyGenerator: () => {
        return crypto.randomUUID();
      },
      defaultKeyLength: 36, // 或密钥长度
    }),
  ],
});

如果 API 密钥通过 customAPIKeyValidator 验证,我们仍然需要将其与数据库中的密钥进行比较。 不过,通过提供这个自定义方法,你可以提升验证性能, 并且所有无效密钥都可以在不访问数据库的情况下快速拒绝。

元数据

允许你为 API 密钥存储元数据,例如订阅计划信息。

请确保在插件选项中未禁用 metadata:

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "@better-auth/api-key"

export const auth = betterAuth({
  plugins: [
    apiKey({
      enableMetadata: true,
    }),
  ],
});

然后你就可以将信息存储到 API 密钥对象的 metadata 字段中。

create-api-key.ts
import { auth } from "@/lib/auth"

const apiKey = await auth.api.createApiKey({
  body: {
    metadata: { 
      plan: "premium", 
    }, 
  },
});

之后你可以从 API 密钥对象中读取 metadata。

get-api-key.ts
import { auth } from "@/lib/auth"

const apiKey = await auth.api.getApiKey({
  body: {
    keyId: "your_api_key_id_here",
  },
});

console.log(apiKey.metadata.plan); // "premium"