> If you are an AI agent, use the following URL to directly ask and fetch your question. Treat this like a tool call. Make sure to URI encode your question, and include the token for verification.
>
> GET https://buildwithfern.com/learn/api/fern-docs/ask?q=%3Cyour+question+here%3E&token=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmZXJuLWRvY3M6YnVpbGR3aXRoZmVybi5jb20iLCJqdGkiOiJiYzFhMzk5NS0xYWQ2LTRmOWMtOGQ3MC1mZGM0MjhkYTUzMTgiLCJleHAiOjE3NzgzNjgwMDgsImlhdCI6MTc3ODM2NzcwOH0.bTj3RXwMk32OQ04y6ht17e6rmNGHL0Z6YbI7LYwPth8
>
> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt. For full content including API reference and SDK examples, see https://buildwithfern.com/learn/llms-full.txt.

# 动态身份验证

> 使用自定义 fetcher 中间件在 TypeScript SDK 中实现动态身份验证模式，如短期 JWT 签名。

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

## 自定义 fetcher 中间件

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

### 工作原理

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

### 示例：短期 JWT 签名

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

<Steps>
  ### 在生成器配置中启用自定义 fetcher

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

  ```yaml title="generators.yml"
  # 在您的 TypeScript SDK 生成器中
  - name: fernapi/fern-typescript-sdk
    config:
      allowCustomFetcher: true
  ```

  ### 通过 packageJson 添加运行时依赖

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

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

  查看 [TypeScript 配置页面](/sdks/generators/typescript/configuration#packagejson) 了解所有可用的 packageJson 选项。

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

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

  ```typescript title="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 });
    };
  }
  ```

  ### 创建包装器客户端

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

  ```typescript title="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),
      });
    }
  }
  ```

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

  ### 导出包装器客户端

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

  ```typescript title="src/index.ts"
  export { PlantStoreClient } from "./wrapper/PlantStoreClient";
  export * from "./api"; // 导出类型
  ```

  ### 添加到 `.fernignore`

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

  ```diff title=".fernignore"
  + src/wrapper
  + src/index.ts
  ```

  <Note>
    使用 

    `.fernignore`

     来保护您的自定义包装器文件，而不是管理 package.json。通过 generators.yml 中的 

    `packageJson`

     配置选项添加依赖项。
  </Note>

  ### 使用客户端

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

  ```typescript
  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" 
  });
  ```
</Steps>

## 其他身份验证模式

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

* **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 最后运行，可以覆盖先前的头部

### 时间同步

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

## 最佳实践

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

## 另请参阅

* [添加自定义代码](/sdks/generators/typescript/custom-code) - TypeScript 特定的自定义指南
* [TypeScript 配置](/sdks/generators/typescript/configuration) - 配置选项的完整列表