API 密钥

API 密钥插件允许您为您的应用程序创建和管理 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 密钥插件选项列表 此处

创建 API 密钥

POST
/api-key/create
Notes

您可以通过使用服务器方法来调整更具体的 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,});
PropDescriptionType
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,    },});
PropDescriptionType
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});
PropDescriptionType
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",});
PropDescriptionType
keyId
要更新的 API 密钥的 ID。
string
name?
密钥的名称。
string

结果

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


删除 API 密钥

POST
/api-key/delete
Notes

此端点试图从用户的角度删除 API 密钥。它将检查用户的 ID 是否与密钥所有者匹配,以允许删除它。如果您想在不进行这些检查的情况下删除密钥,我们建议您使用 ORM 直接变异您的数据库。

const { data, error } = await authClient.apiKey.delete({    keyId: "some-api-key-id", // required});
PropDescriptionType
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 密钥插件选项中参考以下限流默认配置。

一个默认值的示例:

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 将被重置。

剩余计数、补充和过期

剩余计数是 API 密钥被禁用前的剩余请求数。
补充间隔是每天补充 remaining 计数的间隔(毫秒)。
过期时间是 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, // 或者任何长度
    }),
  ],
});

如果从您的 customAPIKeyValidator 验证了 API 密钥,我们仍然必须将其与数据库的密钥匹配。 但是,通过提供此自定义函数,您可以改进 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 密钥插件选项

apiKeyHeaders string | string[];

检查 API 密钥的标头名称。默认为 x-api-key

customAPIKeyGetter (ctx: GenericEndpointContext) => string | null

从上下文中获取 API 密钥的自定义函数。

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

验证 API 密钥的自定义函数。

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

生成 API 密钥的自定义函数。

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

自定义起始字符配置。

defaultKeyLength number

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

defaultPrefix string

API 密钥的前缀。

注意:我们建议您在前缀后附加下划线,以使前缀更容易识别。(例如 hello_

maximumPrefixLength number

前缀的最大长度。

minimumPrefixLength number

前缀的最小长度。

requireName boolean

是否要求 API 密钥有名称。默认为 false

maximumNameLength number

名称的最大长度。

minimumNameLength number

名称的最小长度。

enableMetadata boolean

是否为 API 密钥启用元数据。

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

自定义密钥过期。

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

自定义限流。

schema InferOptionSchema<ReturnType<typeof apiKeySchema>>

API 密钥插件的自定义架构。

disableSessionForAPIKeys boolean

API 密钥可以代表有效的会话,因此如果我们在请求标头中找到有效的 API 密钥,我们会自动为用户模拟一个会话。

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

API 密钥的权限。

阅读有关权限的更多信息 此处

disableKeyHashing boolean

禁用 API 密钥的哈希。

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


权限

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 = {
  [resourceType: string]: string[];
};

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

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

架构

表:apiKey

Field NameTypeKeyDescription
idstringAPI 密钥的 ID。
namestringAPI 密钥的名称。
startstringAPI 密钥的起始字符。在 UI 中显示 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您想与密钥一起存储的任何附加元数据。