跳到导航

动态身份验证

以 Markdown 格式查看

您的 API 可能需要动态身份验证,其中需要为每个请求生成或刷新凭据,例如签名短期 JWT 或轮换令牌。TypeScript SDK 通过自定义 fetcher 中间件支持这种模式。

自定义 fetcher 中间件

在 TypeScript SDK 中实现动态身份验证的推荐方式是使用自定义 fetcher。这充当所有请求的中间件,允许您在单个位置注入身份验证逻辑,而无需重写单个方法。

工作原理

当您在生成器配置中启用 allowCustomFetcher 时,生成的 SDK 在客户端选项中接受 fetcher 参数。此 fetcher 包装所有 HTTP 请求,为您提供身份验证逻辑的单个注入点。

示例:短期 JWT 签名

以下是如何通过令牌记忆化实现 JWT 签名:

1

在生成器配置中启用自定义 fetcher

在您的 generators.yml 中添加 allowCustomFetcher: true

generators.yml
# 在您的 TypeScript SDK 生成器中
- name: fern-typescript-sdk
config:
allowCustomFetcher: true
2

通过 packageJson 添加运行时依赖

使用 packageJson 配置选项添加 jsonwebtoken 依赖,以便它合并到生成的 package.json 中:

generators.yml
# 在您的 TypeScript SDK 生成器中
- name: fern-typescript-sdk
config:
packageJson:
dependencies:
jsonwebtoken: "^9.0.0"
devDependencies:
"@types/jsonwebtoken": "^9.0.0"

查看 TypeScript 配置页面 了解所有可用的 packageJson 选项。

3

创建带有 JWT 签名的自定义 fetcher

创建一个包装默认 fetcher 并注入 JWT 身份验证的 fetcher 函数:

src/wrapper/jwtFetcher.ts
import * as jwt from "jsonwebtoken";
import { fetcher as defaultFetcher, type FetchFunction } from "../core/fetcher";
export function createJwtFetcher(privateKey: string): FetchFunction {
// 缓存令牌以避免在每个请求上重新生成
let cachedToken: { value: string; expiresAt: number } | undefined;
return async (args) => {
const now = Math.floor(Date.now() / 1000);
// 如果令牌已过期或即将过期(2 秒内),则重新生成令牌
if (!cachedToken || cachedToken.expiresAt - 2 <= now) {
const payload = {
iat: now,
exp: now + 15, // 令牌有效期 15 秒
};
const token = jwt.sign(payload, privateKey, { algorithm: "RS256" });
cachedToken = { value: token, expiresAt: payload.exp };
}
// 将 JWT 注入请求头
const headers = {
...(args.headers ?? {}),
Authorization: `Bearer ${cachedToken.value}`,
};
// 使用修改后的头部调用默认 fetcher
return defaultFetcher({ ...args, headers });
};
}
4

创建包装器客户端

扩展生成的客户端以使用自定义 fetcher:

src/wrapper/PlantStoreClient.ts
import { PlantStoreClient as FernClient } from "../Client";
import { createJwtFetcher } from "./jwtFetcher";
// 从生成的客户端构造函数推断确切的选项类型
type FernClientOptions = ConstructorParameters<typeof FernClient>[0];
// 接受除 'fetcher' 之外的所有选项,并添加我们的 'privateKey'
type Options = Omit<FernClientOptions, "fetcher"> & { privateKey: string };
export class PlantStoreClient extends FernClient {
constructor(options: Options) {
// 提取 privateKey 并将所有其他选项传递给父类
const { privateKey, ...clientOptions } = options;
super({
...clientOptions,
fetcher: createJwtFetcher(privateKey),
});
}
}

这种模式使用 ConstructorParameters 从生成的客户端推断确切的选项类型,确保与所有客户端选项(headers、timeoutInSeconds、maxRetries 等)的兼容性,而无需硬编码它们。这使包装器在生成器添加新选项时保持向前兼容。

5

导出包装器客户端

更新您的 index.ts 以导出包装器而不是生成的客户端:

src/index.ts
export { PlantStoreClient } from "./wrapper/PlantStoreClient";
export * from "./api"; // 导出类型
6

添加到 .fernignore

保护您的自定义代码不被覆盖:

.fernignore
+ src/wrapper
+ src/index.ts
使用 .fernignore 来保护您的自定义包装器文件,而不是管理 package.json。通过 generators.yml 中的 packageJson 配置选项添加依赖项。
7

使用客户端

用户可以使用客户端,在所有请求上自动进行 JWT 签名:

import { PlantStoreClient } from "plant-store-sdk";
const client = new PlantStoreClient({
privateKey: process.env.PRIVATE_KEY,
environment: "https://api.plantstore.com",
});
// JWT 在每个请求上自动签名和注入
const plant = await client.plants.get("monstera-123");
const newPlant = await client.plants.create({
name: "Fiddle Leaf Fig",
species: "Ficus lyrata"
});

其他身份验证模式

这种相同的模式适用于其他动态身份验证场景:

  • OAuth 令牌刷新:在每个请求之前自动刷新过期的访问令牌
  • HMAC(基于哈希的消息身份验证代码)签名:基于请求内容使用 HMAC 签名对请求进行签名
  • 轮换 API 密钥:基于速率限制在多个 API 密钥之间切换
  • 基于时间的令牌:生成包含时间戳或随机数的令牌

重要考虑事项

自定义 fetcher 要求

  • 生成器配置:必须在您的 generators.yml 中启用 allowCustomFetcher 选项,以便 fetcher 参数在 BaseClientOptions 中可用
  • 导入路径:从 ../core/fetcher(或生成的 SDK 中的适当路径)导入默认 fetcher,以使用您的自定义逻辑包装它
  • 保留所有参数:在包装默认 fetcher 时,确保传递所有参数以保持与 SDK 内部行为的兼容性

安全考虑事项

  • 仅服务器端:永远不要在浏览器环境中暴露私钥。使用私钥进行 JWT 签名应该只在服务器端代码(Node.js、Deno、Bun)中进行
  • 安全密钥存储:永远不要硬编码私钥;使用环境变量或安全密钥管理系统
  • 避免双重身份验证:如果您的 API 在 Fern 定义中已经使用了 bearer token 身份验证,请小心不要覆盖现有的 Authorization 头部。考虑使用不同的头部名称或有条件地设置头部

性能和并发

  • 令牌记忆化:缓存令牌以避免在每个请求上重新生成它们。上面的示例缓存令牌并在过期前 2 秒刷新它们
  • 线程安全:所示的记忆化模式在 JavaScript 的单线程事件循环中对并发请求是安全的,但在其他环境中要注意竞态条件
  • 宽限期:在令牌过期前稍早刷新令牌(例如,提前 2 秒)以避免令牌在请求处理期间过期的边缘情况

头部合并

  • 保留现有头部:在注入身份验证头部时,始终展开现有头部以避免覆盖 SDK 或用户设置的头部
  • 头部优先级:头部按顺序合并:SDK 默认值 → 客户端选项 → 请求选项 → 自定义 fetcher。您的自定义 fetcher 最后运行,可以覆盖先前的头部

时间同步

  • 时钟漂移:注意客户端和服务器之间潜在的时钟漂移。考虑为令牌过期检查添加容差
  • 时间戳精度:对 iatexp 声明使用 Unix 时间戳(自纪元以来的秒数)以符合 JWT 标准

最佳实践

  • 适当缓存令牌:在安全性(较短的令牌生命周期)和性能(较少频繁的重新生成)之间取得平衡
  • 优雅处理错误:为身份验证失败和令牌刷新错误实现重试逻辑
  • 彻底测试:确保您的包装器处理所有边缘情况,包括并发请求、令牌过期和网络故障
  • 监控令牌使用:记录令牌生成和刷新事件以帮助调试生产中的身份验证问题

另请参阅