API 密钥

API密钥插件允许你为应用程序创建和管理API密钥。它提供了一种通过验证API密钥来对API请求进行认证和授权的方式。

功能

安装

将插件添加到服务器

auth.ts
import { betterAuth } from "better-auth"
import { apiKey } from "better-auth/plugins"
 
const auth = betterAuth({
    plugins: [ 
        apiKey() 
    ] 
})

迁移数据库

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

npx @better-auth/cli migrate

查看Schema部分以手动添加字段。

添加客户端插件

auth-client.ts
import { createAuthClient } from "better-auth/client"
import { apiKeyClient } from "better-auth/client/plugins"
 
const authClient = createAuthClient({
    plugins: [ 
        apiKeyClient() 
    ] 
})

使用方法

你可以在这里查看API密钥插件选项的列表。

创建API密钥

POST
/api-key/create
const { data: apiKey, error } = await authClient.apiKey.create({
    name: "My API Key",
    expiresIn: 60 * 60 * 24 * 7, // 7天
    prefix: "my_app",
    metadata: {
        tier: "premium",
    },
});

所有API密钥都分配给一个用户。如果你在服务器上创建API密钥,而没有访问头信息,你必须传递userId属性。这是与API密钥关联的用户的ID。

属性

所有属性都是可选的。但是,如果你传递了refillAmount,你也必须传递refillInterval,反之亦然。

  • name?: API密钥的名称。
  • expiresIn?: API密钥的过期时间(以秒为单位)。如果不提供,API密钥将永不过期。
  • prefix?: API密钥的前缀。这用于在数据库中标识API密钥。
  • metadata?: API密钥的元数据。这用于存储有关API密钥的附加信息。
仅服务器属性
  • remaining?: API密钥的剩余请求次数。如果为null,则密钥使用没有上限。
  • refillAmount?: 重填API密钥的remaining计数的数量。
  • refillInterval?: 重填API密钥的间隔(以毫秒为单位)。
  • rateLimitTimeWindow?: 计算每个请求的时间窗口(以毫秒为单位)。一旦达到rateLimitMax,请求将被拒绝,直到timeWindow过去,此时时间窗口将被重置。
  • rateLimitMax?: 在rateLimitTimeWindow内允许的最大请求数。
  • rateLimitEnabled?: 是否为API密钥启用速率限制。
  • permissions?: API密钥的权限,结构为将资源类型映射到允许操作数组的记录。
const example = {
  projects: ["read", "read-write"]
}
  • userId?: 与API密钥关联的用户ID。创建API密钥时,你必须传递将拥有该密钥的用户的头信息。但是,如果你没有头信息,你可以传递此字段,这将允许你绕过对头信息的需求。

结果

它将返回包含用于使用的key值的ApiKey对象。 否则,如果抛出异常,它将抛出APIError


验证API密钥

POST
/api-key/verify
const { valid, error, key } = await auth.api.verifyApiKey({
  body: {
    key: "your_api_key_here",
  },
});
 
//带权限检查
const { valid, error, key } = await auth.api.verifyApiKey({
  body: {
    key: "your_api_key_here",
    permissions: {
      projects: ["read", "read-write"]
    }
  },
});

属性

  • key: 要验证的API密钥
  • permissions?: 用于检查API密钥权限的权限。

结果

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

获取API密钥

GET
/api-key/get
const key = await auth.api.getApiKey({
  body: {
    keyId: "your_api_key_id_here",
  },
});

属性

  • keyId: 要获取信息的API密钥ID。

结果

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

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

更新API密钥

POST
/api-key/update
const { data: apiKey, error } = await authClient.apiKey.update({
  keyId: "your_api_key_id_here",
  name: "New API Key Name",
  enabled: false,
});

属性

客户端
  • keyId: 要更新的API密钥ID。

  • name?: 更新密钥名称。

    仅服务器
  • userId?: 更新拥有此密钥的用户ID。

  • name?: 更新密钥名称。

  • enabled?: 更新API密钥是否启用。

  • remaining?: 更新剩余计数。

  • refillAmount?: 更新每个间隔重填remaining计数的数量。

  • refillInterval?: 更新重填remaining计数的间隔。

  • metadata?: 更新API密钥的元数据。

  • expiresIn?: 更新API密钥的过期时间。以秒为单位。

  • rateLimitEnabled?: 更新速率限制器是否启用。

  • rateLimitTimeWindow?: 更新速率限制器的时间窗口。

  • rateLimitMax?: 更新他们在速率限制时间窗口内可以发出的最大请求数。

结果

如果失败,抛出APIError。 否则,你将收到API密钥的详细信息,除了key值本身。


删除API密钥

POST
/api-key/delete
const { data: result, error } = await authClient.apiKey.delete({
  keyId: "your_api_key_id_here",
});

属性

  • keyId: 要删除的API密钥ID。

结果

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

type Result = {
  success: boolean;
};

列出API密钥

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

结果

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

type Result = ApiKey[]

删除所有过期API密钥

此函数将删除所有具有过期日期的API密钥。

DELETE
/api-key/delete-all-expired-api-keys
await auth.api.deleteAllExpiredApiKeys();

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


从API密钥创建会话

每当调用Better Auth中的端点并且头信息中有有效的API密钥时,我们将自动创建一个模拟会话来代表用户。

默认的头信息键是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: ({ctx, key}) => {
          if(key.endsWith("_super_secret_api_key")) {
            return true;
          } else {
            return false;
          }
      },
    })
  ]
});

如果从你的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

一个验证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

前缀的最小长度。

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密钥插件的自定义schema。

disableSessionForAPIKeys boolean

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

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

API密钥的权限。

这里阅读更多关于权限的信息。


Schema

表格: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你想与密钥一起存储的任何其他元数据。

权限

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密钥的权限中必须存在所有所需的权限才能使验证成功。