API 密钥

API Key 插件允许您为应用程序创建和管理 API 密钥。它通过验证 API 密钥来提供对 API 请求进行身份验证和授权的方法。

功能特性

安装步骤

将插件添加到服务器

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "better-auth/plugins"

export const auth = betterAuth({
    plugins: [ 
        apiKey() 
    ] 
})

迁移数据库

运行迁移或生成模式,以向数据库添加必要的字段和表。

npx @better-auth/cli migrate
npx @better-auth/cli generate

请参阅模式部分以手动添加字段。

添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { apiKeyClient } from "better-auth/client/plugins"

export const authClient = createAuthClient({
    plugins: [ 
        apiKeyClient() 
    ] 
})

使用方法

您可以在此处查看 API Key 插件选项的完整列表:API Key 插件选项

创建 API 密钥

POST
/api-key/create
备注

您可以通过使用服务器端方法来调整更具体的 API 密钥配置。

const { data, error } = await authClient.apiKey.create({    name: 'project-api-key',    expiresIn: 60 * 60 * 24 * 7,    prefix: 'project-api-key',    metadata: { someKey: 'someValue' },    permissions,});
属性描述类型
name?
API 密钥的名称。
string
expiresIn?
API 密钥的过期时间(秒)。
number
prefix?
API 密钥的前缀。
string
metadata?
API 密钥的元数据。
any | null
permissions?
API 密钥的权限。
Record<string, string[]>
API 密钥会被分配给一个用户。

结果

它将返回包含 key 值的 ApiKey 对象供你使用。 否则如果抛出错误,将抛出 APIError


验证 API 密钥

POST
/api-key/verify
const permissions = { // 要检查的权限是可选的。  projects: ["read", "read-write"],}const data = await auth.api.verifyApiKey({    body: {        key: "your_api_key_here", // required        permissions,    },});
属性描述类型
key
要验证的密钥。
string
permissions?
要验证的权限。可选。
Record<string, string[]>

结果

type Result = {
  valid: boolean;
  error: { message: string; code: string } | null;
  key: Omit<ApiKey, "key"> | null;
};

获取 API 密钥

GET
/api-key/get
const { data, error } = await authClient.apiKey.get({    id: "some-api-key-id", // required});
属性描述类型
id
API 密钥的 ID。
string

结果

你将收到关于 API 密钥详情的所有信息,除了 key 值本身。 如果失败,将抛出 APIError

type Result = Omit<ApiKey, "key">;

更新 API 密钥

POST
/api-key/update
const { data, error } = await authClient.apiKey.update({    keyId: "some-api-key-id", // required    name: "some-api-key-name",});
属性描述类型
keyId
要更新的 API 密钥 ID。
string
name?
密钥的名称。
string

结果

如果失败,将抛出 APIError。 否则,您将收到 API 密钥的详细信息(不包括 key 值本身)。


删除 API 密钥

POST
/api-key/delete
备注

此端点尝试从用户的角度删除 API 密钥。它将检查用户 ID 是否与密钥所有者匹配以确定是否可以删除。如果您希望绕过这些检查删除密钥,建议您使用 ORM 直接操作数据库。

const { data, error } = await authClient.apiKey.delete({    keyId: "some-api-key-id", // required});
属性描述类型
keyId
要删除的 API 密钥的 ID。
string

结果

如果失败,将抛出 APIError。 否则,您将收到:

type Result = {
  success: boolean;
};

列出 API 密钥

GET
/api-key/list
const { data, error } = await authClient.apiKey.list();

结果

如果失败,将抛出 APIError。 否则,您将收到:

type Result = ApiKey[];

删除所有过期的 API 密钥

此功能将删除所有已过期的 API 密钥。

POST
/api-key/delete-all-expired-api-keys
const data = await auth.api.deleteAllExpiredApiKeys();

每次调用任何 apiKey 插件端点时,我们都会自动删除过期的 API 密钥,但每次调用都有 10 秒的冷却时间限制,以防止对数据库进行多次调用。


通过 API 密钥创建会话

每当调用 Better Auth 中的端点且请求头中包含有效的 API 密钥时,系统会自动创建一个模拟会话来代表用户。

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

默认的请求头键为 x-api-key,但可以通过在插件选项中设置 apiKeyHeaders 来更改。

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

或者,您可以选择向插件选项传递一个 apiKeyGetter 函数,该函数将通过 GenericEndpointContext 调用,您应从中返回 API 密钥,如果请求无效则返回 null

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

速率限制

每个 API 密钥都可以拥有自己的速率限制设置,但内置的速率限制仅适用于特定 API 密钥的验证过程。 对于所有其他端点/方法,您应当使用 Better Auth 的内置速率限制

您可以在 API Key 插件选项的默认配置中参考速率限制的默认设置。

一个默认值的示例:

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

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

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

const apiKey = await auth.api.createApiKey({
  body: {
    rateLimitEnabled: true,
    rateLimitTimeWindow: 1000 * 60 * 60 * 24, // 1 天
    rateLimitMax: 10, // 每天 10 次请求
  },
  headers: user_headers,
});

它是如何工作的?

对于每个请求,计数器(内部称为 requestCount)会递增。 如果达到 rateLimitMax,请求将被拒绝,直到 timeWindow 时间窗口过去,此时 timeWindow 将被重置。

剩余次数、补充和过期

剩余次数(remaining)是指在 API 密钥被禁用之前还可以进行的请求次数。 补充间隔(refill interval)是以毫秒为单位的间隔,在此间隔内 remaining 计数会按天补充。 过期时间(expiration time)是 API 密钥的过期日期。

它是如何工作的?

剩余次数:

每当使用 API 密钥时,remaining 计数会被更新。 如果 remaining 计数为 null,则表示密钥使用没有上限。 否则,remaining 计数会减 1。 如果 remaining 计数为 0,则 API 密钥将被禁用并移除。

refillInterval 与 refillAmount:

每当创建 API 密钥时,refillIntervalrefillAmount 都会被设置为 null。 这意味着该 API 密钥不会自动补充。 但是,如果设置了 refillIntervalrefillAmount,那么 API 密钥将根据设置进行补充。

过期时间:

每当创建 API 密钥时,expiresAt 会被设置为 null。 这意味着该 API 密钥永不过期。 但是,如果设置了 expiresIn,那么 API 密钥将在 expiresIn 时间后过期。

自定义密钥生成与验证

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

以下是一个示例:

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 属性来指定生成密钥的长度。

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

如果 API 密钥通过你的 customAPIKeyValidator 验证,我们仍然需要将其与数据库中的密钥进行匹配。 但是,通过提供这个自定义函数,你可以提高 API 密钥验证过程的性能, 因为所有无效的密钥都可以在不查询数据库的情况下被直接拒绝。

元数据

我们允许您在 API 密钥旁边存储元数据。这对于存储密钥相关信息非常有用,例如订阅计划等。

要存储元数据,请确保您没有在插件选项中禁用元数据功能。

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

然后,您可以在 API 密钥对象的 metadata 字段中存储元数据。

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

之后,您可以从 API 密钥对象中检索元数据。

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

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

API Key 插件选项

apiKeyHeaders string | string[];

用于检查 API Key 的请求头名称。默认为 x-api-key

customAPIKeyGetter (ctx: GenericEndpointContext) => string | null

一个自定义函数,用于从上下文(context)中获取 API Key。

customAPIKeyValidator (options: { ctx: GenericEndpointContext; key: string; }) => boolean | Promise<boolean>

一个自定义函数,用于验证 API Key。

customKeyGenerator (options: { length: number; prefix: string | undefined; }) => string | Promise<string>

一个自定义函数,用于生成 API Key。

startingCharactersConfig { shouldStore?: boolean; charactersLength?: number; }

自定义起始字符配置。

defaultKeyLength number

API Key 的长度。越长越好。默认为 64。(不包括前缀长度)

defaultPrefix string

API Key 的前缀。

注意:建议你在前缀后附加一个下划线,以使前缀更易于识别。(例如 hello_

maximumPrefixLength number

前缀的最大长度。

minimumPrefixLength number

前缀的最小长度。

requireName boolean

是否要求为 API Key 提供一个名称。默认为 false

maximumNameLength number

名称的最大长度。

minimumNameLength number

名称的最小长度。

enableMetadata boolean

是否为 API Key 启用元数据功能。

keyExpiration { defaultExpiresIn?: number | null; disableCustomExpiresTime?: boolean; minExpiresIn?: number; maxExpiresIn?: number; }

自定义密钥过期设置。

rateLimit { enabled?: boolean; timeWindow?: number; maxRequests?: number; }

自定义速率限制。

schema InferOptionSchema<ReturnType<typeof apiKeySchema>>

API Key 插件的自定义 schema。

disableSessionForAPIKeys boolean

一个 API Key 可以代表一个有效的会话(session),因此如果我们在请求头中发现有效的 API Key,我们会自动为用户模拟一个会话。

permissions { defaultPermissions?: Statements | ((userId: string, ctx: GenericEndpointContext) => Statements | Promise<Statements>) }

API Key 的权限。

在此处阅读更多关于权限的信息 here

disableKeyHashing boolean

禁用 API Key 的哈希处理。

⚠️ 安全警告:强烈建议不要禁用哈希。 以明文形式存储 API Key 会使它们容易受到数据库泄露的影响,可能会暴露所有用户的 API Key。


权限

API 密钥可以关联权限,允许您在细粒度级别控制访问。权限的结构是一个资源类型到允许操作数组的记录。

设置默认权限

您可以配置默认权限,这些权限将应用于所有新创建的 API 密钥:

export const auth = betterAuth({
  plugins: [
    apiKey({
      permissions: {
        defaultPermissions: {
          files: ["read"],
          users: ["read"],
        },
      },
    }),
  ],
});

您也可以提供一个动态返回权限的函数:

export const auth = betterAuth({
  plugins: [
    apiKey({
      permissions: {
        defaultPermissions: async (userId, ctx) => {
          // 获取用户角色或其他数据以确定权限
          return {
            files: ["read"],
            users: ["read"],
          };
        },
      },
    }),
  ],
});

创建带权限的 API 密钥

创建 API 密钥时,您可以指定自定义权限:

const apiKey = await auth.api.createApiKey({
  body: {
    name: "My API Key",
    permissions: {
      files: ["read", "write"],
      users: ["read"],
    },
    userId: "userId",
  },
});

验证 API 密钥及其所需权限

验证 API 密钥时,您可以检查它是否具有所需的权限:

const result = await auth.api.verifyApiKey({
  body: {
    key: "your_api_key_here",
    permissions: {
      files: ["read"],
    },
  },
});

if (result.valid) {
  // API 密钥有效且具有所需权限
} else {
  // API 密钥无效或缺少所需权限
}

更新 API 密钥权限

您可以更新现有 API 密钥的权限:

const apiKey = await auth.api.updateApiKey({
  body: {
    keyId: existingApiKeyId,
    permissions: {
      files: ["read", "write", "delete"],
      users: ["read", "write"],
    },
  },
  headers: user_headers,
});

权限结构

权限采用基于资源的结构:

type Permissions = {
};

// 示例:
const permissions = {
  files: ["read", "write", "delete"],
  users: ["read"],
  projects: ["read", "write"],
};

在验证 API 密钥时,API 密钥的权限中必须包含所有必需的权限,验证才能成功。

数据库表结构

表名: apiKey

字段名称类型Key描述
idstringAPI 密钥的 ID
namestringAPI 密钥的名称
startstringAPI 密钥的起始字符。在用户界面中显示 API 密钥的前几个字符,方便用户识别。
prefixstringAPI 密钥前缀。以纯文本形式存储。
keystring-经过哈希处理的 API 密钥本身。
userIdstring与该 API 密钥关联的用户 ID。
refillIntervalnumber密钥补充间隔时间(毫秒)。
refillAmountnumber密钥剩余请求次数的补充数量。
lastRefillAtDate密钥最后一次补充的时间。
enabledboolean-API 密钥是否启用。
rateLimitEnabledboolean-是否启用 API 密钥的速率限制。
rateLimitTimeWindownumber速率限制的时间窗口(毫秒)。
rateLimitMaxnumber在 `rateLimitTimeWindow` 时间窗口内允许的最大请求数。
requestCountnumber-在速率限制时间窗口内已发出的请求数量。
remainingnumber剩余的请求次数。
lastRequestDate最后一次使用该密钥发出请求的时间。
expiresAtDate密钥的过期时间。
createdAtDate-API 密钥的创建时间。
updatedAtDate-API 密钥的最后更新时间。
permissionsstring密钥的权限。
metadataObject希望与密钥一起存储的任何附加元数据。