---
title: 搭配 AWS 使用 WIF
url: https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/aws
description: 透過 Workload Identity Federation 與 STS 簽發的身分權杖，讓 Lambda、EC2、ECS 或 EKS 上的 AWS 工作負載向 Claude API 進行驗證。
---

AWS 工作負載可以透過交換由 AWS 簽署的 OIDC 身分權杖（identity token）向 Claude API 進行驗證，而無需使用靜態 API 金鑰。建議的路徑是呼叫 AWS STS [`GetWebIdentityToken`](https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html) API，只要工作負載擁有 AWS 憑證，此方法在任何地方都適用：Lambda、EC2、ECS 與 EKS。EKS 工作負載也可以改用 [Kubernetes 投射權杖路徑](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/aws#use-eks-projected-service-account-tokens)，其設定步驟較少，但僅能在 pod 內運作。

本指南將說明這兩種路徑。關於底層概念（服務帳戶、聯合簽發者與聯合規則），請參閱 [Workload Identity Federation](https://platform.claude.com/docs/zh-TW/manage-claude/workload-identity-federation)。

## 先決條件

* 熟悉 [WIF 概念](https://platform.claude.com/docs/zh-TW/manage-claude/workload-identity-federation#concepts)：「service accounts」（服務帳戶）、「federation issuers」（聯合簽發者）與「federation rules」（聯合規則）。
* 一個已附加 IAM 角色的 AWS 工作負載（EKS pod、ECS 任務、Lambda 函式或 EC2 執行個體）。
* 工作負載中可使用 `aws` CLI 或 AWS SDK。
* 擁有在 Claude Console 中為您的 Anthropic 組織建立服務帳戶、聯合簽發者與聯合規則的權限。

## 使用 STS web identity 權杖（建議）

AWS STS `GetWebIdentityToken` API 會回傳一個由 AWS 簽署、用以聲明呼叫者 IAM 身分的 OIDC 權杖。由於它使用工作負載環境中既有的 AWS 憑證，因此同一套整合即可涵蓋 Lambda、EC2、ECS 與 EKS。

### 設定 AWS

<Steps>
  <Step title="為帳戶啟用對外 web identity 聯合">
    這是一個帳戶層級的旗標，預設為關閉。在 AWS 主控台中，開啟 **IAM**，選擇 **Account settings**，然後啟用 **Outbound web identity federation**。若要以程式方式啟用：

    ```bash
    python3 -c "import boto3; boto3.client('iam').enable_outbound_web_identity_federation()"
    ```

    若未啟用此功能，呼叫 `GetWebIdentityToken` 將會失敗並回傳 `OutboundWebIdentityFederationDisabledException`。
  </Step>

  <Step title="授予工作負載的 IAM 角色呼叫此 API 的權限">
    將此政策附加到您的 Lambda 函式、EC2 執行個體或 ECS 任務所使用的 IAM 角色：

    ```json
    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["sts:GetWebIdentityToken"],
          "Resource": "*"
        }
      ]
    }
    ```
  </Step>

  <Step title="找出您帳戶的 STS 簽發者 URL">
    啟用對外聯合後，**IAM > Account settings** 頁面會顯示一個 **Get Token Issuer URL** 欄位，其值的格式為 `https://<uuid>.tokens.sts.global.api.aws`。此 URL 對您的 AWS 帳戶而言是唯一的；請複製它以供下一步使用。若要以程式方式取得：

    ```bash
    python3 -c "import boto3; print(boto3.client('iam').get_outbound_web_identity_federation_info())"
    ```
  </Step>
</Steps>

### 設定 Anthropic

在 Claude Console 中，前往 [Workload identity](https://platform.claude.com/settings/workload-identity-federation) 頁面（`platform.claude.com/settings/workload-identity-federation`），點擊 **Connect workload**，然後選擇 **AWS** 圖塊。精靈會引導您完成註冊簽發者、建立服務帳戶以及建立聯合規則的步驟。

精靈會為您建立這些資源。無論您是在精靈中輸入這些值，還是將它們傳送至 [Admin API](https://platform.claude.com/docs/zh-TW/manage-claude/wif-admin-api)，請使用下列值：

**聯合簽發者：** 註冊您在前一步驟中複製的每帳戶專屬 STS 簽發者 URL。它公開了一個公用的 JWKS 端點，因此請使用探索（discovery）模式。

```json
{
  "name": "aws-sts",
  "issuer_url": "https://<uuid>.tokens.sts.global.api.aws",
  "jwks": { "type": "discovery" }
}
```

**聯合規則：** 比對您傳遞給 `GetWebIdentityToken` 的 audience，以及 `sub` 宣告中呼叫角色的 IAM 角色 ARN。`sub` 的值是呼叫此 API 之工作負載的 IAM 角色 ARN，格式為 `arn:aws:iam::<account>:role/<role-name>`。該權杖還帶有一個 `https://sts.amazonaws.com/` 宣告，其中包含 `aws_account`、`org_id`、`principal_id` 以及您傳入的任何 `request_tags`；您可以透過規則的 `claims` 對應表或 CEL `condition` 來比對這些值，以進行更精細的控制。

```json
{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "arn:aws:iam::123456789012:role/inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

請在工作負載允許的範圍內盡可能具體。比對確切的角色 ARN，且僅在多個 IAM 角色應對應到同一個 Anthropic 服務帳戶時，才放寬 `subject_prefix`（例如放寬為 `arn:aws:iam::123456789012:role/*`）。

### 取得並使用權杖

以 `https://api.anthropic.com` 作為 audience 呼叫 `GetWebIdentityToken`，然後透過 `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`) 將結果傳給 SDK 的聯合憑證。除了 Java 之外（請參閱該範例中的註解），該選項接受的是權杖提供者，因此 SDK 每次重新整理時都會再次呼叫 STS。

<Note>
  `GetWebIdentityToken` 僅在區域性 STS 端點上提供。如果您收到 `'STS' object has no attribute 'get_web_identity_token'` 或類似的錯誤，請將您的 STS 用戶端固定到某個區域（例如 `boto3.client("sts", region_name="us-east-1")`），並確認您的 AWS SDK 版本夠新，已包含此 API。
</Note>

<CodeGroup>
  ```bash cURL
  JWT=$(aws sts get-web-identity-token \
    --region us-east-1 \
    --audience "https://api.anthropic.com" \
    --signing-algorithm RS256 \
    --duration-seconds 900 \
    --query WebIdentityToken --output text)

  RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    --data @- <<JSON
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$JWT",
    "federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
    "organization_id": "$ANTHROPIC_ORGANIZATION_ID",
    "service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
    "workspace_id": "$ANTHROPIC_WORKSPACE_ID"
  }
  JSON
  )

  ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)

  curl https://api.anthropic.com/v1/messages \
    -H "authorization: Bearer $ACCESS_TOKEN" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "messages": [{"role": "user", "content": "Hello from AWS"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os

  import anthropic
  import boto3
  from anthropic import WorkloadIdentityCredentials


  def get_sts_web_identity_token() -> str:
      sts = boto3.client("sts", region_name="us-east-1")
      resp = sts.get_web_identity_token(
          Audience=["https://api.anthropic.com"],
          SigningAlgorithm="RS256",
          DurationSeconds=900,
      )
      return resp["WebIdentityToken"]


  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=get_sts_web_identity_token,
          federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
          organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
          service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
          workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
      ),
  )

  message = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello from AWS"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
  import { STSClient, GetWebIdentityTokenCommand } from "@aws-sdk/client-sts";

  const sts = new STSClient({ region: "us-east-1" });

  async function getStsWebIdentityToken(): Promise<string> {
    const out = await sts.send(
      new GetWebIdentityTokenCommand({
        Audience: ["https://api.anthropic.com"],
        SigningAlgorithm: "RS256",
        DurationSeconds: 900
      })
    );
    return out.WebIdentityToken!;
  }

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: getStsWebIdentityToken,
      federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
      organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
      serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
      workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
      baseURL: "https://api.anthropic.com",
      fetch
    })
  });

  const message = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello from AWS" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  ctx := context.TODO()
  cfg, err := config.LoadDefaultConfig(ctx, config.WithRegion("us-east-1"))
  if err != nil {
  	panic(err)
  }
  stsClient := sts.NewFromConfig(cfg)

  getStsToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
  	out, err := stsClient.GetWebIdentityToken(ctx, &sts.GetWebIdentityTokenInput{
  		Audience:         []string{"https://api.anthropic.com"},
  		SigningAlgorithm: "RS256",
  		DurationSeconds:  aws.Int32(900),
  	})
  	if err != nil {
  		return "", err
  	}
  	return *out.WebIdentityToken, nil
  })

  client := anthropic.NewClient(
  	option.WithFederationTokenProvider(getStsToken, option.FederationOptions{
  		FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
  		OrganizationID:   os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
  		ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
  		WorkspaceID:      os.Getenv("ANTHROPIC_WORKSPACE_ID"),
  	}),
  )

  message, err := client.Messages.New(ctx, anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from AWS")),
  	},
  })
  if err != nil {
  	panic(err)
  }
  for _, block := range message.Content {
  	if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  		fmt.Println(textBlock.Text)
  		break
  	}
  }
  ```

  ```java Java
  import com.anthropic.config.AuthenticationConfig;
  import com.anthropic.config.AuthenticationType;
  import com.anthropic.config.IdentityTokenConfig;
  import com.anthropic.config.InMemoryProfileConfigProvider;
  import com.anthropic.config.ProfileConfig;
  // ...
  import software.amazon.awssdk.regions.Region;
  import software.amazon.awssdk.services.sts.StsClient;
  import software.amazon.awssdk.services.sts.model.GetWebIdentityTokenRequest;

  void main() {
      StsClient sts = StsClient.builder().region(Region.US_EAST_1).build();

      // Java SDK 接受權杖本身或檔案路徑，而非函式；使用
      // source("value") 時，path(...) 存放權杖。SDK 會在每次重新整理
      // Anthropic 存取權杖時傳送同一個權杖，一旦該權杖
      // 過期，或（若含 jti 宣告）已被使用過，就會失敗。對於長時間執行的
      // 程序，請傳入 source("file")，並在每次重新整理前
      // 將新權杖寫入該檔案。
      String stsToken = sts.getWebIdentityToken(
                      GetWebIdentityTokenRequest.builder()
                              .audience("https://api.anthropic.com")
                              .signingAlgorithm("RS256")
                              .durationSeconds(900)
                              .build())
              .webIdentityToken();

      AnthropicClient client = AnthropicOkHttpClient.builder()
              .configurationProvider(InMemoryProfileConfigProvider.of(ProfileConfig.builder()
                      .organizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
                      .workspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
                      .authentication(AuthenticationConfig.builder()
                              .type(AuthenticationType.OIDC_FEDERATION)
                              .federationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
                              .serviceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
                              .identityToken(IdentityTokenConfig.builder()
                                      .source("value")
                                      .path(stsToken)
                                      .build())
                              .build())
                      .build()))
              .build();

      var message = client.messages().create(MessageCreateParams.builder()
              .model(Model.CLAUDE_OPUS_5_5)
              .maxTokens(1024)
              .addUserMessage("Hello from AWS")
              .build());

      IO.println(message.content());
  }
  ```

  ```csharp C#
  using Amazon.SecurityToken;
  using Amazon.SecurityToken.Model;
  // ...
  using Anthropic.Credentials;
  // ...

  var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
  {
      FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
      OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
      ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
      WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
      IdentityTokenProvider = new StsTokenProvider(),
  });
  using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });

  var message = await client.Messages.Create(new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello from AWS" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class StsTokenProvider : IIdentityTokenProvider
  {
      private readonly AmazonSecurityTokenServiceClient _sts = new(Amazon.RegionEndpoint.USEast1);

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          var resp = await _sts.GetWebIdentityTokenAsync(new GetWebIdentityTokenRequest
          {
              Audience = ["https://api.anthropic.com"],
              SigningAlgorithm = "RS256",
              DurationSeconds = 900,
          }, ct);
          return resp.WebIdentityToken;
      }
  }
  ```

  ```bash CLI
  TOKEN_FILE=$(mktemp)
  aws sts get-web-identity-token \
    --region us-east-1 \
    --audience "https://api.anthropic.com" \
    --signing-algorithm RS256 \
    --duration-seconds 900 \
    --query WebIdentityToken --output text > "$TOKEN_FILE"

  export ANTHROPIC_IDENTITY_TOKEN_FILE="$TOKEN_FILE"
  # ANTHROPIC_FEDERATION_RULE_ID、ANTHROPIC_ORGANIZATION_ID，以及
  # ANTHROPIC_SERVICE_ACCOUNT_ID 和 ANTHROPIC_WORKSPACE_ID 皆從環境變數讀取
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello from AWS"}'
  ```

  ```php PHP
  use Anthropic\Client;
  use Anthropic\Lib\Credentials\CredentialResult;
  use Anthropic\Lib\Credentials\IdentityTokenProvider;
  use Anthropic\Lib\Credentials\TokenCache;
  use Anthropic\Lib\Credentials\WorkloadIdentityCredentials;
  use Aws\Sts\StsClient;

  $sts = new StsClient(['region' => 'us-east-1', 'version' => 'latest']);

  class StsIdentityToken implements IdentityTokenProvider
  {
      public function __construct(private StsClient $sts) {}

      public function getIdentityToken(): string
      {
          return $this->sts->getWebIdentityToken([
              'Audience' => ['https://api.anthropic.com'],
              'SigningAlgorithm' => 'RS256',
              'DurationSeconds' => 900,
          ])['WebIdentityToken'];
      }
  }

  $client = new Client(credentials: new CredentialResult(
      provider: new TokenCache(new WorkloadIdentityCredentials(
          identityProvider: new StsIdentityToken($sts),
          federationRuleId: getenv('ANTHROPIC_FEDERATION_RULE_ID'),
          organizationId: getenv('ANTHROPIC_ORGANIZATION_ID'),
          serviceAccountId: getenv('ANTHROPIC_SERVICE_ACCOUNT_ID'),
          workspaceId: getenv('ANTHROPIC_WORKSPACE_ID') ?: null,
      )),
  ));

  $message = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello from AWS']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"
  require "aws-sdk-sts"

  sts = Aws::STS::Client.new(region: "us-east-1")
  client = Anthropic::Client.new(
    credentials: Anthropic::WorkloadIdentityCredentials.new(
      identity_token_provider: -> {
        sts.get_web_identity_token(
          audience: ["https://api.anthropic.com"],
          signing_algorithm: "RS256",
          duration_seconds: 900,
        ).web_identity_token
      },
      federation_rule_id: ENV.fetch("ANTHROPIC_FEDERATION_RULE_ID"),
      organization_id: ENV.fetch("ANTHROPIC_ORGANIZATION_ID"),
      service_account_id: ENV.fetch("ANTHROPIC_SERVICE_ACCOUNT_ID"),
      workspace_id: ENV["ANTHROPIC_WORKSPACE_ID"],
    ),
  )

  message = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello from AWS"}]
  )
  puts message.content.find { it.type == :text }.text
  ```
</CodeGroup>

### 驗證設定

從工作負載內部，直接交換一個由 STS 簽發的權杖並檢視回應：

```bash cURL
JWT=$(aws sts get-web-identity-token \
  --region us-east-1 \
  --audience "https://api.anthropic.com" \
  --signing-algorithm RS256 \
  --duration-seconds 900 \
  --query WebIdentityToken --output text)

curl -sS https://api.anthropic.com/v1/oauth/token \
  -H "content-type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
    \"assertion\": \"$JWT\",
    \"federation_rule_id\": \"fdrl_...\",
    \"organization_id\": \"00000000-0000-0000-0000-000000000000\",
    \"service_account_id\": \"svac_...\",
    \"workspace_id\": \"wrkspc_...\"
  }" | jq
```

成功的交換會回傳一個以 `sk-ant-oat01-` 開頭的 `access_token`，以及一個以秒為單位的 `expires_in` 值。如果交換失敗並回傳不透明的 `401` `authentication_error` 回應（訊息為 `Authentication failed`），請查看 Claude Console 中的[驗證歷史記錄頁面](https://platform.claude.com/settings/workload-identity-federation?tab=history)以了解拒絕原因，並參閱[疑難排解失敗的交換](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference#troubleshoot-a-failed-exchange)；AWS 端最常見的原因是 `iss` 不符（帳戶專屬的 STS 簽發者 URL 必須與已註冊的 `issuer_url` 完全相符）。

## 使用 EKS 投射的服務帳戶權杖

如果您的工作負載在 EKS pod 中執行，您可以略過 STS 呼叫，直接從磁碟讀取 Kubernetes 投射的服務帳戶權杖。Kubernetes 原生會將一個與 OIDC 相容的權杖投射到 pod 中，而 SDK 可以從檔案路徑讀取它，因此不需要權杖提供者可呼叫物件。此路徑比 STS 路徑少了兩個 AWS 設定步驟，但僅能在 pod 內運作；其底層機制與[通用 Kubernetes 整合](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/kubernetes)相同。

此路徑另外需要一個已[啟用 IAM OIDC 提供者](https://docs.aws.amazon.com/eks/latest/userguide/enable-iam-roles-for-service-accounts.html)的 EKS 叢集，以及對該叢集的 `kubectl` 存取權限。

### 設定您的 EKS 叢集

<Steps>
  <Step title="找出您叢集的 OIDC 簽發者 URL">
    每個 EKS 叢集都有一個唯一的 OIDC 簽發者。使用 AWS CLI 取得它：

    ```bash CLI
    aws eks describe-cluster \
      --name <cluster-name> \
      --query "cluster.identity.oidc.issuer" \
      --output text
    ```

    輸出看起來像 `https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...`。您將在下一節中把此 URL 註冊為聯合簽發者。
  </Step>

  <Step title="建立服務帳戶並投射一個以 Anthropic 為 audience 的權杖">
    EKS pod identity webhook 會偵測 `eks.amazonaws.com/role-arn` 註解，並自動投射一個 `aud: sts.amazonaws.com` 的權杖，將其路徑公開為 `AWS_WEB_IDENTITY_TOKEN_FILE`。該權杖用於 AWS 角色擔任（role assumption）。對於 Anthropic 交換，請投射第二個 `audience: https://api.anthropic.com` 的權杖，並將其掛載到專用路徑。

    ```yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: inference-worker
      namespace: inference
      annotations:
        eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/inference-worker
    ```

    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: inference-worker
      namespace: inference
    spec:
      serviceAccountName: inference-worker
      volumes:
        - name: anthropic-token
          projected:
            sources:
              - serviceAccountToken:
                  audience: https://api.anthropic.com
                  expirationSeconds: 3600
                  path: token
      containers:
        - name: app
          image: your-registry/inference-worker:latest
          env:
            - name: ANTHROPIC_IDENTITY_TOKEN_FILE
              value: /var/run/secrets/anthropic.com/token
            - name: ANTHROPIC_FEDERATION_RULE_ID
              value: fdrl_...
            - name: ANTHROPIC_ORGANIZATION_ID
              value: 00000000-0000-0000-0000-000000000000
            - name: ANTHROPIC_SERVICE_ACCOUNT_ID
              value: svac_...
            - name: ANTHROPIC_WORKSPACE_ID  # required when the rule covers multiple workspaces
              value: wrkspc_...
          volumeMounts:
            - name: anthropic-token
              mountPath: /var/run/secrets/anthropic.com
              readOnly: true
    ```
  </Step>

  <Step title="留意權杖的宣告結構">
    投射的權杖是一個由您叢集的 OIDC 簽發者簽署的「JSON Web Token」（JSON 網路權杖），即 JWT。其 `sub` 宣告遵循 Kubernetes 慣例 `system:serviceaccount:<namespace>:<service-account-name>`：

    ```json
    {
      "iss": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
      "sub": "system:serviceaccount:inference:inference-worker",
      "aud": ["https://api.anthropic.com"],
      "kubernetes.io": {
        "namespace": "inference",
        "serviceaccount": { "name": "inference-worker", "uid": "..." }
      },
      "exp": 1775527120,
      "iat": 1775523520
    }
    ```

    `serviceAccountToken` 投射會將 `aud` 設為 `https://api.anthropic.com`。位於 `AWS_WEB_IDENTITY_TOKEN_FILE` 的另一個由 IRSA 注入的權杖帶有 `aud: sts.amazonaws.com`，用於 AWS API 呼叫，而非此交換。
  </Step>
</Steps>

### 設定 Anthropic

在 Claude Console 中，前往 [Workload identity](https://platform.claude.com/settings/workload-identity-federation) 頁面（`platform.claude.com/settings/workload-identity-federation`），點擊 **Connect workload**，然後選擇 **AWS** 圖塊。精靈會引導您完成註冊簽發者、建立服務帳戶以及建立聯合規則的步驟。

精靈會為您建立這些資源。無論您是在精靈中輸入這些值，還是將它們傳送至 [Admin API](https://platform.claude.com/docs/zh-TW/manage-claude/wif-admin-api)，請使用下列值：

**聯合簽發者：** EKS 簽發者公開了一個公用的 JWKS 端點，因此請使用探索模式。簽發者 URL 必須與權杖的 `iss` 宣告完全一致。每個叢集註冊一個簽發者。

```json
{
  "name": "prod-eks-uswest2",
  "issuer_url": "https://oidc.eks.us-west-2.amazonaws.com/id/6FA42E7BFDE8549CB...",
  "jwks": { "type": "discovery" }
}
```

**聯合規則：** 比對 Kubernetes 的 `sub` 宣告與 Anthropic audience `https://api.anthropic.com`。（請投射一個具有該 audience 的專用服務帳戶權杖；不要重複使用 IRSA 預設的 `sts.amazonaws.com` 權杖。）

```json
{
  "name": "prod-inference",
  "issuer_id": "fdis_...",
  "match": {
    "subject_prefix": "system:serviceaccount:inference:inference-worker",
    "audience": "https://api.anthropic.com"
  },
  "target": { "type": "service_account", "service_account_id": "svac_..." },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

請在工作負載允許的範圍內盡可能具體。僅在命名空間中的每個服務帳戶都應對應到同一個 Anthropic 服務帳戶時，才將 `subject_prefix` 放寬為 `system:serviceaccount:inference:*`（結尾的 `*` 使其成為前綴比對）。

### 取得並使用權杖

在 pod 內部，投射的權杖位於 `/var/run/secrets/anthropic.com/token`（在 Pod 規格中公開為 `ANTHROPIC_IDENTITY_TOKEN_FILE`）。將該檔案傳遞給 SDK 的聯合憑證，SDK 便會處理交換與重新整理。

<CodeGroup>
  ```bash cURL
  JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")

  RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    --data @- <<JSON
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$JWT",
    "federation_rule_id": "$ANTHROPIC_FEDERATION_RULE_ID",
    "organization_id": "$ANTHROPIC_ORGANIZATION_ID",
    "service_account_id": "$ANTHROPIC_SERVICE_ACCOUNT_ID",
    "workspace_id": "$ANTHROPIC_WORKSPACE_ID"
  }
  JSON
  )

  ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)

  curl https://api.anthropic.com/v1/messages \
    -H "authorization: Bearer $ACCESS_TOKEN" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 1024,
      "messages": [{"role": "user", "content": "Hello from EKS"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os

  import anthropic
  from anthropic import IdentityTokenFile, WorkloadIdentityCredentials

  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=IdentityTokenFile(
              os.environ["ANTHROPIC_IDENTITY_TOKEN_FILE"]
          ),
          federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
          organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
          service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
          workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
      ),
  )

  message = client.messages.create(
      model="claude-opus-5-5",
      max_tokens=1024,
      messages=[{"role": "user", "content": "Hello from EKS"}],
  )
  print(next(block.text for block in message.content if block.type == "text"))
  ```

  ```typescript TypeScript
  import Anthropic from "@anthropic-ai/sdk";
  import { oidcFederationProvider } from "@anthropic-ai/sdk/lib/credentials/oidc-federation";
  import { identityTokenFromFile } from "@anthropic-ai/sdk/lib/credentials/identity-token";

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: identityTokenFromFile(process.env.ANTHROPIC_IDENTITY_TOKEN_FILE!),
      federationRuleId: process.env.ANTHROPIC_FEDERATION_RULE_ID!,
      organizationId: process.env.ANTHROPIC_ORGANIZATION_ID!,
      serviceAccountId: process.env.ANTHROPIC_SERVICE_ACCOUNT_ID,
      workspaceId: process.env.ANTHROPIC_WORKSPACE_ID,
      baseURL: "https://api.anthropic.com",
      fetch
    })
  });

  const message = await client.messages.create({
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{ role: "user", content: "Hello from EKS" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  tokenPath := os.Getenv("ANTHROPIC_IDENTITY_TOKEN_FILE")

  readToken := option.IdentityTokenFunc(func(ctx context.Context) (string, error) {
  	raw, err := os.ReadFile(tokenPath)
  	if err != nil {
  		return "", fmt.Errorf("read identity token: %w", err)
  	}
  	return string(raw), nil
  })

  client := anthropic.NewClient(
  	option.WithFederationTokenProvider(readToken, option.FederationOptions{
  		FederationRuleID: os.Getenv("ANTHROPIC_FEDERATION_RULE_ID"),
  		OrganizationID:   os.Getenv("ANTHROPIC_ORGANIZATION_ID"),
  		ServiceAccountID: os.Getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"),
  		WorkspaceID:      os.Getenv("ANTHROPIC_WORKSPACE_ID"),
  	}),
  )

  message, err := client.Messages.New(context.TODO(), anthropic.MessageNewParams{
  	Model:     anthropic.ModelClaudeOpus5_5,
  	MaxTokens: 1024,
  	Messages: []anthropic.MessageParam{
  		anthropic.NewUserMessage(anthropic.NewTextBlock("Hello from EKS")),
  	},
  })
  if err != nil {
  	panic(err)
  }
  for _, block := range message.Content {
  	if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  		fmt.Println(textBlock.Text)
  		break
  	}
  }
  ```

  ```java Java
  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  var message = client.messages().create(MessageCreateParams.builder()
          .model(Model.CLAUDE_OPUS_5_5)
          .maxTokens(1024)
          .addUserMessage("Hello from EKS")
          .build());

  IO.println(message.content());
  ```

  ```csharp C#
  // 讀取 ANTHROPIC_FEDERATION_RULE_ID、ANTHROPIC_ORGANIZATION_ID、
  // ANTHROPIC_SERVICE_ACCOUNT_ID、ANTHROPIC_WORKSPACE_ID 及 ANTHROPIC_IDENTITY_TOKEN_FILE
  // （取自 pod 的環境變數）。
  using var client = new AnthropicClient();

  var message = await client.Messages.Create(new()
  {
      Model = Model.ClaudeOpus5_5,
      MaxTokens = 1024,
      Messages = [new() { Role = Role.User, Content = "Hello from EKS" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }
  ```

  ```bash CLI
  # 讀取 ANTHROPIC_FEDERATION_RULE_ID、ANTHROPIC_ORGANIZATION_ID、
  # ANTHROPIC_SERVICE_ACCOUNT_ID、ANTHROPIC_WORKSPACE_ID 及 ANTHROPIC_IDENTITY_TOKEN_FILE
  ant messages create \
    --model claude-opus-5-5 \
    --max-tokens 1024 \
    --message '{role: user, content: "Hello from EKS"}'
  ```

  ```php PHP
  use Anthropic\Client;

  // 讀取 ANTHROPIC_FEDERATION_RULE_ID、ANTHROPIC_ORGANIZATION_ID、
  // ANTHROPIC_SERVICE_ACCOUNT_ID、ANTHROPIC_WORKSPACE_ID 及 ANTHROPIC_IDENTITY_TOKEN_FILE
  $client = new Client();

  $message = $client->messages->create(
      model: 'claude-opus-5-5',
      maxTokens: 1024,
      messages: [['role' => 'user', 'content' => 'Hello from EKS']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"

  # 讀取 ANTHROPIC_FEDERATION_RULE_ID、ANTHROPIC_ORGANIZATION_ID、
  # ANTHROPIC_SERVICE_ACCOUNT_ID、ANTHROPIC_WORKSPACE_ID 及 ANTHROPIC_IDENTITY_TOKEN_FILE
  client = Anthropic::Client.new

  message = client.messages.create(
    model: "claude-opus-5-5",
    max_tokens: 1024,
    messages: [{role: "user", content: "Hello from EKS"}]
  )
  puts message.content.find { it.type == :text }.text
  ```
</CodeGroup>

<Tip>
  Pod 規格已設定 `ANTHROPIC_IDENTITY_TOKEN_FILE`、`ANTHROPIC_FEDERATION_RULE_ID`、`ANTHROPIC_ORGANIZATION_ID`、`ANTHROPIC_SERVICE_ACCOUNT_ID` 與 `ANTHROPIC_WORKSPACE_ID`，因此您可以不帶任何引數建構用戶端，SDK 會自動讀取聯合環境變數。
</Tip>

### 驗證設定

從 pod 內部，直接交換投射的權杖並檢視回應：

```bash cURL
JWT=$(cat "$ANTHROPIC_IDENTITY_TOKEN_FILE")

curl -sS https://api.anthropic.com/v1/oauth/token \
  -H "content-type: application/json" \
  -d "{
    \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",
    \"assertion\": \"$JWT\",
    \"federation_rule_id\": \"$ANTHROPIC_FEDERATION_RULE_ID\",
    \"organization_id\": \"$ANTHROPIC_ORGANIZATION_ID\",
    \"service_account_id\": \"$ANTHROPIC_SERVICE_ACCOUNT_ID\",
    \"workspace_id\": \"$ANTHROPIC_WORKSPACE_ID\"
  }" | jq
```

成功的交換會回傳一個以 `sk-ant-oat01-` 開頭的 `access_token`，以及一個以秒為單位的 `expires_in` 值。如果交換失敗並回傳不透明的 `401` `authentication_error` 回應（訊息為 `Authentication failed`），請查看 Claude Console 中的[驗證歷史記錄頁面](https://platform.claude.com/settings/workload-identity-federation?tab=history)以了解拒絕原因，並參閱[疑難排解失敗的交換](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference#troubleshoot-a-failed-exchange)；EKS 端最常見的原因是投射權杖的 `aud` 與規則不符（請投射帶有 `audience: https://api.anthropic.com` 的權杖，而非 IRSA 預設的 `sts.amazonaws.com`）。

## 限定規則的範圍

<Warning>
  `subject_prefix` 為 `arn:aws:iam::123456789012:role/*` 會比對該帳戶中的每一個 IAM 角色。任何能夠擔任任一相符角色的主體，都可以取得聯合的 Anthropic 權杖。
</Warning>

將規則的 `match` 區塊鎖定在符合您使用情境的最小範圍：

* **固定完整的角色 ARN：** 使用 `subject_prefix: "arn:aws:iam::<account>:role/<role-name>"` 且結尾不加 `*`，使帳戶中的其他角色不會相符。
* **固定帳戶 ID：** 透過 `claims` 對應表或 CEL `condition` 比對權杖 `https://sts.amazonaws.com/` 宣告中的 `aws_account` 欄位，作為防範前綴設定錯誤的縱深防禦檢查。
* **在 EKS 上固定命名空間與服務帳戶：** 使用確切的 `system:serviceaccount:<namespace>:<name>` 值，且在 `system:serviceaccount:` 前綴之後不加 `*`。
* **每個環境使用獨立的規則：** 為正式、預備與開發環境的工作負載分別建立不同的規則，而非放寬單一前綴來涵蓋全部。

## 後續步驟

* 檢閱 [WIF 參考文件](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference)，以取得完整的憑證優先順序、設定檔設定與規則比對參考。
* 對於不在 EKS 上的自行管理 Kubernetes 叢集，請參閱[搭配 Kubernetes 使用 WIF](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/kubernetes)。
