---
title: 搭配 Microsoft Entra ID 使用 WIF
url: https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure
description: 將 Azure 受控識別與 Entra Workload Identity 與 Claude API 建立聯合，讓您的 Azure 工作負載無需靜態 API 金鑰即可呼叫 Claude。
---

Azure 工作負載向 Claude API 進行驗證的方式，是出示由 Microsoft Entra ID 簽發的「JSON Web Token」（JSON 網路權杖），即 JWT，然後將其交換為短效期的 Anthropic 存取權杖。在每個 Azure 平台上，設定流程的形式都相同：

1. **註冊權杖受眾（audience）：** 在您的 Microsoft Entra 租用戶中建立一個應用程式註冊，用來代表 Claude API 受眾。租用戶中的每個工作負載都會為它請求 Entra 權杖。
2. **為您的平台設定身分識別：** 在 VM、VM Scale Sets、App Service、Functions 與 Container Apps 上使用受控識別（managed identity），或在 AKS 上使用 Entra Workload Identity。
3. **設定 Anthropic：** 註冊您租用戶的 Entra 簽發者、建立服務帳戶，並撰寫一條與權杖宣告（claims）相符的聯合規則。
4. **在執行階段交換：** 您的工作負載在 `POST /v1/oauth/token` 將其 Entra 簽發的權杖交換為 `sk-ant-oat01-...` Anthropic 存取權杖，並以此呼叫 Claude。

在這兩條路徑上，您出示給 Anthropic 的權杖都帶有您租用戶專屬的 Entra 簽發者，以及位於 `sub` 與 `oid` 宣告中的受控識別物件 ID；差別只在於工作負載取得該權杖的方式。請依您的工作負載執行位置選擇對應章節：VM、VM Scale Sets、App Service、Functions 或 Container Apps 請參閱[使用受控識別](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#use-a-managed-identity)；AKS 請參閱[在 AKS 上使用 Entra Workload Identity](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#use-entra-workload-identity-on-aks)。

## 先決條件

* 熟悉 [WIF 概念](https://platform.claude.com/docs/zh-TW/manage-claude/workload-identity-federation#concepts)：服務帳戶、聯合簽發者與聯合規則。
* 一個具有指派受控識別權限（或在 AKS 上設定 Entra Workload Identity 權限）的 Azure 訂用帳戶。
* 在您的 Microsoft Entra 租用戶中建立一個應用程式註冊與服務主體的權限（即共用的 Claude API 受眾）。Entra 只會為租用戶中存在的受眾簽發權杖，因此在任何權杖請求成功之前，必須先完成[註冊權杖受眾](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#register-the-token-audience)步驟。
* 您的 Microsoft Entra 租用戶 ID。可在 Azure 入口網站的 **Microsoft Entra ID → Overview → Tenant ID** 下找到。
* 在 Claude Console 中為您的 Anthropic 組織建立服務帳戶、聯合簽發者與聯合規則的權限。

## 註冊權杖受眾

Microsoft Entra ID 只有在所請求的受眾以具有服務主體的應用程式註冊形式存在於您的租用戶中時，才會簽發權杖。請建立一個應用程式註冊來代表 Claude API 受眾；租用戶中的每個工作負載都可以為它請求權杖。若沒有此註冊，權杖請求會失敗並出現「resource not found in tenant」錯誤（受控識別端點回傳 `AADSTS50001`，Entra 權杖端點回傳 `AADSTS500011`）。

```bash
# 建立代表 Claude API audience 的應用程式註冊。
APP_ID=$(az ad app create --display-name claude-api-federation --query appId -o tsv)

# 請求 v2.0 存取權杖，並設定 api://<APP_ID> 識別碼 URI。
az ad app update --id "$APP_ID" \
  --identifier-uris "api://$APP_ID" \
  --set api.requestedAccessTokenVersion=2

# 建立服務主體，讓 audience 能在您的租用戶中解析。
az ad sp create --id "$APP_ID"
```

<Note>
  請使用 `api://<APP_ID>` 識別碼 URI 格式。Entra 將 `https://` 識別碼 URI 限制為您自己租用戶的已驗證網域，因此像 `https://api.anthropic.com` 這樣的 URI 在大多數租用戶中無法註冊；而 `api://<APP_ID>` 在任何地方都會被接受。設定 `requestedAccessTokenVersion: 2` 後，此受眾的權杖為 v2.0，這也是本指南的假設前提。如果您重複使用會發出 v1.0 權杖的既有註冊，請參閱[如果您的權杖是 v1.0](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0)。
</Note>

## 使用受控識別

當您的工作負載在 VM、VM Scale Set、App Service、Functions 或 Container Apps 上執行時，請使用此路徑。工作負載會從平台的本機權杖端點為其指派的受控識別請求一個 Entra 簽發的 JWT，然後將該 JWT 與 Anthropic 進行交換。

### 設定受控識別

<Steps>
  <Step title="附加受控識別">
    在您的 Azure 資源上啟用系統指派或使用者指派的受控識別。在 Azure 入口網站中開啟該資源，前往 **Identity**，然後開啟 **System assigned**（或附加一個使用者指派的識別）。

    識別建立後，請記下其 **Object (principal) ID**。此 GUID 會同時出現在簽發權杖的 `sub` 與 `oid` 宣告中，而您的 Anthropic 聯合規則將以它進行比對。您可以在資源的 **Identity** 頁面找到它；若為使用者指派的識別，則是受控識別資源 **Overview** 頁面上的 **Object (principal) ID**。（受控識別在 Microsoft Entra ID 中只有服務主體，沒有應用程式註冊。）
  </Step>

  <Step title="找到平台的權杖端點">
    附加識別後，平台會公開一個本機權杖端點：

    * **VM 與 VM Scale Sets：** IMDS，位於 `http://169.254.169.254/metadata/identity/oauth2/token`，需帶標頭 `Metadata: true` 與 `api-version=2018-02-01`。
    * **App Service、Functions 與 Container Apps：** `IDENTITY_ENDPOINT` 環境變數中的 URL，需將標頭 `X-IDENTITY-HEADER` 設為 `IDENTITY_HEADER` 的值，並使用 `api-version=2019-08-01`。這些平台上無法連線至 IMDS。

    如果資源有多個使用者指派的受控識別，請在權杖請求中加入 `client_id=<IDENTITY_CLIENT_ID>` 以選擇其中一個。Azure 建議一律指定它。若未指定，結果取決於該資源是否同時啟用了系統指派的識別：若有，請求會靜默地回退至該識別，然後無法通過您聯合規則的 `oid` 比對；若沒有，一旦附加第二個使用者指派的識別，請求就會直接失敗。
  </Step>

  <Step title="解碼範例權杖">
    從端點請求一個權杖並解碼其承載內容，以確認您的聯合規則需要比對的宣告。（解碼指令請參閱[疑難排解失敗的交換](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference#troubleshoot-a-failed-exchange)。）受控識別的 v2.0 權杖帶有以下宣告：

    ```json
    {
      "iss": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
      "sub": "9f8e7d6c-1a2b-3c4d-5e6f-...",
      "aud": "<APP_ID>",
      "oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
      "tid": "<TENANT_ID>",
      "azp": "<IDENTITY_CLIENT_ID>",
      "ver": "2.0",
      "exp": 1775527120
    }
    ```

    | 宣告    | 值                                                                                                                                                  | 何時比對此項                                                                                                                                        |
    | ----- | -------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
    | `oid` | 受控識別的物件 ID，與 `sub` 相同                                                                                                                              | 您想授權某一個特定的受控識別。這是預設做法；[設定 Anthropic](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#configure-anthropic) 中的規則即比對此項。 |
    | `azp` | 呼叫端識別的用戶端 ID                                                                                                                                       | 您想授權共用同一個應用程式註冊的所有工作負載。對受控識別而言，`azp` 對該識別是唯一的，因此等同於 `oid`。                                                                                    |
    | `aud` | 受眾應用程式註冊的用戶端 ID（來自[註冊權杖受眾](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#register-the-token-audience)的 `<APP_ID>` GUID） | 一律比對。規則的 `audience` 欄位必須與權杖的 `aud` 值完全相等。                                                                                                     |
    | `tid` | 您的租用戶 ID                                                                                                                                           | 您想要縱深防禦。簽發者 URL 已經鎖定了租用戶。                                                                                                                     |

    如果解碼後權杖的 `ver` 宣告為 `1.0`，則宣告名稱與值會有所不同。請先參閱[如果您的權杖是 v1.0](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0) 再繼續。
  </Step>
</Steps>

### 設定 Anthropic

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

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

**聯合簽發者：** 在精靈的 **Token issuer** 選擇器中選擇 **v2.0 (login.microsoftonline.com)**。（選擇器預設為 v1；該預設值是為了重複使用仍發出 v1.0 權杖之舊註冊的租用戶而存在。）Entra 會在每個租用戶專屬的簽發者 URL 發佈 OIDC 探索文件，因此請使用探索模式。您建立聯合的每個 Microsoft Entra 租用戶都需要自己的簽發者記錄。

```json
{
  "name": "azure-prod-tenant",
  "issuer_url": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
  "jwks": { "type": "discovery" },
  "max_jwt_lifetime_seconds": 86400
}
```

<Warning>
  受控識別工作負載需要 `max_jwt_lifetime_seconds: 86400`。Azure 簽發的受控識別權杖在 `iat` 與 `exp` 之間最長可達 24 小時，因為它會在該時段內快取每個資源的權杖，且無法強制提前重新整理；而簽發者的 1 小時預設值會拒絕這些權杖，因此交換會失敗並回傳不透明的 `401` `authentication_error` 回應（訊息為 `Authentication failed`）。Connect workload 精靈的 Microsoft Entra 圖塊在建立簽發者時會將 `max_jwt_lifetime_seconds` 設為 `7500`，且建立期間沒有可變更此值的欄位，因此請先完成精靈，然後在 Claude Console 中開啟 [Workload identity](https://platform.claude.com/settings/workload-identity-federation) 頁面上的 **Issuers** 分頁，編輯簽發者，並將值提高到 `86400`。您也可以透過 Admin API 更新簽發者。
</Warning>

接受的效期越長，代表外洩的 Entra 權杖可被交換的時間越長。若權杖外洩，應對手段是停用聯合規則；而嚴格的 `oid` 比對則從一開始就限制了哪些識別可以交換權杖，如[限定規則範圍](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#scope-your-rule)所述。

**聯合規則：** 比對受控識別的物件 ID 與您的租用戶 ID。對於本指南所設定的 v2.0 權杖，`audience` 值為受眾應用程式註冊的用戶端 ID（來自[註冊權杖受眾](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#register-the-token-audience)的 `<APP_ID>` GUID）。請使用您解碼後權杖中確切的 `aud` 值。

```json
{
  "name": "azure-inference-worker",
  "issuer_id": "fdis_...",
  "match": {
    "audience": "<APP_ID>",
    "claims": {
      "oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
      "tid": "<TENANT_ID>"
    }
  },
  "target": {
    "type": "service_account",
    "service_account_id": "svac_..."
  },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

`token_lifetime_seconds` 是交換所回傳之 Anthropic 存取權杖的效期，而非 Entra 權杖的效期；SDK 會為您重新整理它。

### 取得並使用權杖

在執行階段，您的工作負載會擷取其 Entra 權杖，在 `POST /v1/oauth/token` 進行交換，並使用回傳的 bearer 權杖呼叫 Claude。當您提供 `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`) 時，Claude SDK 會處理交換和重新整理迴圈，如以下範例所示。除了 Java 之外（請參閱該範例中的註解），該選項接受一個權杖提供者，SDK 會在每次重新整理時再次呼叫它。cURL 分頁展示原始流程。

範例會從平台的權杖端點擷取受控識別權杖：VM 與 VM Scale Sets 上為 IMDS，App Service、Functions 與 Container Apps 上則為 `IDENTITY_ENDPOINT` 服務。請將 `api://<APP_ID>` 資源值中的 `<APP_ID>` 替換為來自[註冊權杖受眾](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#register-the-token-audience)的受眾應用程式註冊用戶端 ID。

<Tip>
  如果您的工作負載已經使用 Azure Identity 用戶端程式庫，請從中取得 Entra 權杖（使用範圍 `api://<APP_ID>/.default` 的 `DefaultAzureCredential`），而不是直接呼叫權杖端點。請以範例中的方式將其提供給 `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`)。該程式庫會在每個 Azure 平台上選擇正確的端點，包括使用 Entra Workload Identity 的 AKS。
</Tip>

<CodeGroup>
  ```bash cURL
  # 1. 取得 Entra 核發的權杖（受控識別）。
  #    在 VM 或 VM Scale Set 上，請使用 IMDS。若有多個使用者指派的
  #    識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
  ENTRA_TOKEN=$(curl -sS -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://<APP_ID>" \
    | jq -r .access_token)

  #    在 App Service、Functions 或 Container Apps 上，請改用本機權杖
  #    服務（這些環境無法連線至 IMDS）：
  # ENTRA_TOKEN=$(curl -sS -H "X-IDENTITY-HEADER: $IDENTITY_HEADER" \
  #   "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=api://<APP_ID>" \
  #   | jq -r .access_token)

  #    若為使用 Entra Workload Identity 的 AKS，請改用
  #    「在 AKS 上使用 Entra Workload Identity」一節中的兩段式交換。

  # 2. 將其交換為 Anthropic 存取權杖。
  RESPONSE=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    -d @- <<JSON
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$ENTRA_TOKEN",
    "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)

  # 3. 使用 bearer 權杖呼叫 Claude API。
  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 Azure"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os

  import anthropic
  import requests
  from anthropic import WorkloadIdentityCredentials

  # audience 應用程式註冊的識別碼 URI（請參閱「註冊權杖 audience」）。
  AUDIENCE = "api://<APP_ID>"


  def fetch_entra_token() -> str:
      """Fetch a managed identity token from the platform's token endpoint."""
      # 若有多個使用者指派的身分識別，請新增 client_id=<IDENTITY_CLIENT_ID>
      # 至請求參數以選擇其中一個。
      if endpoint := os.environ.get("IDENTITY_ENDPOINT"):
          # App Service、Functions、Container Apps
          response = requests.get(
              endpoint,
              headers={"X-IDENTITY-HEADER": os.environ["IDENTITY_HEADER"]},
              params={"api-version": "2019-08-01", "resource": AUDIENCE},
              timeout=5,
          )
      else:
          # VM 或 VM Scale Set：Azure Instance Metadata Service (IMDS)
          response = requests.get(
              "http://169.254.169.254/metadata/identity/oauth2/token",
              headers={"Metadata": "true"},
              params={"api-version": "2018-02-01", "resource": AUDIENCE},
              timeout=5,
          )
      response.raise_for_status()
      return response.json()["access_token"]


  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=fetch_entra_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 Azure"}],
  )
  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";

  // audience 應用程式註冊的識別碼 URI（請參閱「Register the token audience」）。
  const AUDIENCE = "api://<APP_ID>";

  async function fetchEntraToken(): Promise<string> {
    // App Service、Functions 與 Container Apps 會注入 IDENTITY_ENDPOINT；
    // VM 與 VM Scale Sets 則使用 IMDS。
    // 若有多個使用者指派的身分識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
    const identityEndpoint = process.env.IDENTITY_ENDPOINT;
    const url = identityEndpoint
      ? `${identityEndpoint}?api-version=2019-08-01&resource=${AUDIENCE}`
      : `http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=${AUDIENCE}`;
    const headers: Record<string, string> = identityEndpoint
      ? { "X-IDENTITY-HEADER": process.env.IDENTITY_HEADER! }
      : { Metadata: "true" };
    const response = await fetch(url, { headers });
    const body = (await response.json()) as { access_token: string };
    return body.access_token;
  }

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: fetchEntraToken,
      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 Azure" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  package main

  import (
  	"context"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"os"

  	"github.com/anthropics/anthropic-sdk-go"
  	"github.com/anthropics/anthropic-sdk-go/option"
  )

  // audience 應用程式註冊的識別碼 URI（請參閱「註冊權杖 audience」）。
  const audience = "api://<APP_ID>"

  // fetchEntraToken 會從平台的權杖端點取得 managed identity（受控識別）權杖：
  // 在 VM 與 VM Scale Sets 上為 IMDS，在 App Service、Functions 與 Container Apps 上
  // 則為 IDENTITY_ENDPOINT 服務。
  func fetchEntraToken(ctx context.Context) (string, error) {
  	// 若有多個使用者指派的識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
  	tokenURL := "http://169.254.169.254/metadata/identity/oauth2/token" +
  		"?api-version=2018-02-01&resource=" + audience
  	header, value := "Metadata", "true"
  	if endpoint := os.Getenv("IDENTITY_ENDPOINT"); endpoint != "" {
  		tokenURL = endpoint + "?api-version=2019-08-01&resource=" + audience
  		header, value = "X-IDENTITY-HEADER", os.Getenv("IDENTITY_HEADER")
  	}
  	req, err := http.NewRequestWithContext(ctx, http.MethodGet, tokenURL, nil)
  	if err != nil {
  		return "", err
  	}
  	req.Header.Set(header, value)
  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		return "", fmt.Errorf("call token endpoint: %w", err)
  	}
  	defer resp.Body.Close()
  	var body struct {
  		AccessToken string `json:"access_token"`
  	}
  	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
  		return "", fmt.Errorf("decode token response: %w", err)
  	}
  	return body.AccessToken, nil
  }

  func main() {
  	client := anthropic.NewClient(
  		option.WithFederationTokenProvider(fetchEntraToken, 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 Azure")),
  		},
  	})
  	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 com.fasterxml.jackson.databind.ObjectMapper;
  import java.net.URI;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;

  void main() throws Exception {
      HttpClient http = HttpClient.newHttpClient();
      // audience 應用程式註冊的識別碼 URI（請參閱「註冊權杖 audience」）。
      String audience = "api://<APP_ID>";
      // App Service、Functions 與 Container Apps 會注入 IDENTITY_ENDPOINT；
      // VM 與 VM Scale Sets 則使用 IMDS。
      // 若有多個使用者指派的身分識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
      String identityEndpoint = System.getenv("IDENTITY_ENDPOINT");
      HttpRequest tokenRequest = identityEndpoint != null
              ? HttpRequest.newBuilder(URI.create(identityEndpoint + "?api-version=2019-08-01&resource=" + audience))
                      .header("X-IDENTITY-HEADER", System.getenv("IDENTITY_HEADER"))
                      .build()
              : HttpRequest.newBuilder(URI.create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=" + audience))
                      .header("Metadata", "true")
                      .build();

      // Java SDK 接受權杖本身或檔案路徑，而非函式；使用
      // source("value") 時，path(...) 存放權杖。SDK 會在每次重新整理
      // Anthropic 存取權杖時傳送同一個權杖，一旦該權杖
      // 過期，或（若含 jti 宣告）已被使用過，就會失敗。對於長時間執行的
      // 程序，請傳入 source("file")，並在每次重新整理前
      // 將新權杖寫入該檔案。
      var response = http.send(tokenRequest, HttpResponse.BodyHandlers.ofString());
      String entraToken = new ObjectMapper().readTree(response.body()).get("access_token").asText();

      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(entraToken)
                                      .build())
                              .build())
                      .build()))
              .build();

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

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

  ```csharp C#
  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 EntraTokenProvider(),
  });
  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 Azure" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class EntraTokenProvider : IIdentityTokenProvider
  {
      // audience 應用程式註冊的識別碼 URI（請參閱「註冊權杖 audience」）。
      private const string Audience = "api://<APP_ID>";

      private static readonly HttpClient httpClient = new();

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          // App Service、Functions 與 Container Apps 會注入 IDENTITY_ENDPOINT；
          // VM 與 VM Scale Sets 則使用 IMDS。
          // 若有多個使用者指派的身分識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
          var identityEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
          using var request = identityEndpoint is not null
              ? new HttpRequestMessage(HttpMethod.Get,
                  $"{identityEndpoint}?api-version=2019-08-01&resource={Audience}")
              {
                  Headers = { { "X-IDENTITY-HEADER", Environment.GetEnvironmentVariable("IDENTITY_HEADER") } },
              }
              : new HttpRequestMessage(HttpMethod.Get,
                  $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={Audience}")
              {
                  Headers = { { "Metadata", "true" } },
              };
          using var response = await httpClient.SendAsync(request, ct);
          response.EnsureSuccessStatusCode();
          using var json = await JsonDocument.ParseAsync(
              await response.Content.ReadAsStreamAsync(ct), default, ct);
          return json.RootElement.GetProperty("access_token").GetString()!;
      }
  }
  ```

  ```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;

  // audience 應用程式註冊的識別碼 URI（請參閱「註冊權杖 audience」）。
  const AUDIENCE = 'api://<APP_ID>';

  class EntraIdentityToken implements IdentityTokenProvider
  {
      public function getIdentityToken(): string
      {
          // App Service、Functions 與 Container Apps 會注入 IDENTITY_ENDPOINT；
          // VM 與 VM Scale Sets 則使用 IMDS。
          // 若有多個使用者指派的身分識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
          $identityEndpoint = getenv('IDENTITY_ENDPOINT');
          if ($identityEndpoint !== false) {
              $url = $identityEndpoint . '?api-version=2019-08-01&resource=' . AUDIENCE;
              $header = 'X-IDENTITY-HEADER: ' . getenv('IDENTITY_HEADER');
          } else {
              $url = 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=' . AUDIENCE;
              $header = 'Metadata: true';
          }
          $context = stream_context_create([
              'http' => ['header' => $header . "\r\n"],
          ]);
          $body = json_decode(file_get_contents($url, false, $context), true);
          return $body['access_token'];
      }
  }

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

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

  ```ruby Ruby
  require "anthropic"
  require "json"
  require "net/http"

  # audience 應用程式註冊的識別碼 URI（請參閱「註冊權杖 audience」）。
  AUDIENCE = "api://<APP_ID>"

  def fetch_entra_token
    # App Service、Functions 與 Container Apps 會注入 IDENTITY_ENDPOINT；
    # VM 與 VM Scale Sets 則使用 IMDS。
    # 若有多個使用者指派的身分識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
    if (endpoint = ENV["IDENTITY_ENDPOINT"])
      url = "#{endpoint}?api-version=2019-08-01&resource=#{AUDIENCE}"
      headers = {"X-IDENTITY-HEADER" => ENV.fetch("IDENTITY_HEADER")}
    else
      url = "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=#{AUDIENCE}"
      headers = {"Metadata" => "true"}
    end
    response = Net::HTTP.get(URI(url), headers)
    JSON.parse(response).fetch("access_token")
  end

  credentials = Anthropic::WorkloadIdentityCredentials.new(
    identity_token_provider: -> { fetch_entra_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"]
  )
  client = Anthropic::Client.new(credentials: credentials)

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

  ```bash CLI
  # 將 Entra 核發的存取權杖寫入 CLI 可讀取的檔案。
  # 此處以 VM 或 VM Scale Set (IMDS) 為例。若在 App Service、Functions 或
  # Container Apps 上，請改從 "$IDENTITY_ENDPOINT?api-version=2019-08-01&resource=api://<APP_ID>" 取得，
  # 並加上 -H "X-IDENTITY-HEADER: $IDENTITY_HEADER"。
  # 若有多個使用者指派的身分識別，請附加 &client_id=<IDENTITY_CLIENT_ID>。
  ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
  trap 'rm -f "$ANTHROPIC_IDENTITY_TOKEN_FILE"' EXIT
  curl -sS -H "Metadata: true" \
    "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=api://<APP_ID>" \
    | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
  export ANTHROPIC_IDENTITY_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 Azure"}'
  ```
</CodeGroup>

### 驗證設定

從您的 Azure 資源執行[取得並使用權杖](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#acquire-and-use-the-token)中所示的 cURL 交換，並確認 `POST /v1/oauth/token` 回傳 `200`，其中包含以 `sk-ant-oat01-` 開頭的 `access_token`，以及以秒為單位的 `expires_in` 值。如果交換失敗並回傳不透明的 `401` `authentication_error` 回應（訊息為 `Authentication failed`），請在 Claude Console 的[驗證歷程記錄頁面](https://platform.claude.com/settings/workload-identity-federation?tab=history)查看拒絕原因，然後解碼 Entra 權杖（指令請參閱[疑難排解失敗的交換](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference#troubleshoot-a-failed-exchange)），並檢查最常見的 Azure 端原因：

* **簽發者不符：** 已註冊的 `issuer_url` 必須與權杖的 `iss` 宣告完全相符。v2.0 權杖帶有 `https://login.microsoftonline.com/<TENANT_ID>/v2.0`；如果解碼後的 `ver` 宣告為 `1.0`，請參閱[如果您的權杖是 v1.0](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0)。
* **權杖效期：** 受控識別權杖在 `iat` 與 `exp` 之間最長可達 24 小時。如果簽發者仍為精靈的 `7500`（或 1 小時預設值），請依[設定 Anthropic](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#configure-anthropic) 所述將 `max_jwt_lifetime_seconds` 提高至 `86400`。
* **受眾不符：** 規則的 `audience` 必須與權杖的 `aud` 完全相等：對於本指南所設定的 v2.0 權杖，即為受眾應用程式註冊的用戶端 ID。
* **宣告名稱不符：** 比對權杖未攜帶之宣告的規則永遠不會通過。v1.0 權杖將用戶端 ID 放在 `appid` 而非 `azp`；請參閱[如果您的權杖是 v1.0](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0)。

## 在 AKS 上使用 Entra Workload Identity

當您的工作負載在 AKS pod 中執行時，請使用此路徑。Entra Workload Identity 將 Kubernetes 服務帳戶與使用者指派的受控識別建立聯合：Kubernetes 會將一個服務帳戶權杖（由 AKS 叢集的 OIDC 簽發者簽署）投射到 pod 中 `AZURE_FEDERATED_TOKEN_FILE` 所指的路徑。該投射權杖並非 Entra 簽發的權杖，因此為了維持在本頁所述的 Entra 中介路徑上，工作負載會執行兩段式交換：先在 `https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token`（聯合 `client_credentials` 授與）將投射權杖兌換為 Entra 簽發的存取權杖，然後將該 Entra 權杖作為身分識別權杖傳給 Anthropic SDK。

<Tip>
  AKS pod 也可以選擇跳過 Entra 交換，直接向 Anthropic 出示 Kubernetes 投射的服務帳戶權杖。該路徑會向 Anthropic 註冊您 AKS 叢集的 OIDC 簽發者，而非您的 Entra 租用戶。該流程請參閱[搭配 Kubernetes 使用 WIF](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/kubernetes)。
</Tip>

### 設定 Entra Workload Identity

<Steps>
  <Step title="在叢集上啟用 OIDC 簽發者與工作負載識別">
    啟用工作負載識別會為您安裝 `azure-workload-identity` 變更 webhook；只有在非 AKS 叢集上才需手動部署。請記下叢集的 OIDC 簽發者 URL，供後續步驟建立聯合認證時使用。

    ```bash
    az aks update \
      --resource-group <RESOURCE_GROUP> \
      --name <CLUSTER_NAME> \
      --enable-oidc-issuer \
      --enable-workload-identity

    AKS_OIDC_ISSUER=$(az aks show \
      --resource-group <RESOURCE_GROUP> \
      --name <CLUSTER_NAME> \
      --query oidcIssuerProfile.issuerUrl -o tsv)
    ```
  </Step>

  <Step title="建立使用者指派的受控識別">
    從該識別記下兩個值：**Client ID** 會放入服務帳戶註解（並以 `AZURE_CLIENT_ID` 注入 pod），而 **Object (principal) ID** 會以 `oid` 宣告出現，供您的 Anthropic 聯合規則比對。

    ```bash
    az identity create \
      --resource-group <RESOURCE_GROUP> \
      --name claude-inference-identity \
      --location <LOCATION>

    # 放入 service account 註解中；以 AZURE_CLIENT_ID 注入至 pod。
    IDENTITY_CLIENT_ID=$(az identity show \
      --resource-group <RESOURCE_GROUP> \
      --name claude-inference-identity \
      --query clientId -o tsv)

    # 會以 oid claim 的形式出現，供您的聯合規則比對。
    IDENTITY_OBJECT_ID=$(az identity show \
      --resource-group <RESOURCE_GROUP> \
      --name claude-inference-identity \
      --query principalId -o tsv)
    ```
  </Step>

  <Step title="建立帶註解的 Kubernetes 服務帳戶">
    `azure-workload-identity` webhook 會讀取 `azure.workload.identity/client-id` 註解，將 `AZURE_CLIENT_ID` 注入 pod，而[取得並使用權杖](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#acquire-and-use-the-token-2)中的範例會從環境變數讀取它。

    ```yaml
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: claude-inference
      namespace: inference
      annotations:
        azure.workload.identity/client-id: <IDENTITY_CLIENT_ID>
    ```
  </Step>

  <Step title="在受控識別上建立聯合認證">
    聯合認證會針對該特定服務帳戶信任您叢集的 OIDC 簽發者。`--audience api://AzureADTokenExchange` 值是 Entra 針對傳入 Kubernetes 服務帳戶權杖的固定受眾；它與您先前註冊的 Claude API 受眾無關。

    ```bash
    az identity federated-credential create \
      --resource-group <RESOURCE_GROUP> \
      --identity-name claude-inference-identity \
      --name claude-inference-aks \
      --issuer "$AKS_OIDC_ISSUER" \
      --subject system:serviceaccount:inference:claude-inference \
      --audience api://AzureADTokenExchange
    ```
  </Step>

  <Step title="為 pod 加上標籤並設定其服務帳戶">
    pod 必須帶有 `azure.workload.identity/use: "true"` 標籤，並以帶註解的服務帳戶執行。webhook 接著會將 `AZURE_FEDERATED_TOKEN_FILE`、`AZURE_CLIENT_ID` 與 `AZURE_TENANT_ID` 注入 pod。位於 `AZURE_FEDERATED_TOKEN_FILE` 的檔案包含由 AKS 叢集 OIDC 簽發者簽署的 Kubernetes 投射服務帳戶權杖。

    ```yaml
    apiVersion: v1
    kind: Pod
    metadata:
      name: inference-worker
      namespace: inference
      labels:
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: claude-inference
      containers:
        - name: app
          image: your-registry/inference-worker:latest
    ```
  </Step>

  <Step title="解碼範例權杖">
    您的 Anthropic 聯合規則所看到的權杖並非投射檔案；而是 `client_credentials` 交換所回傳的 Entra 簽發權杖。從帶標籤的 pod 內部，執行[取得並使用權杖](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#acquire-and-use-the-token-2)中 cURL 範例的步驟 1 並解碼結果。它帶有與受控識別路徑相同的宣告形式：

    ```json
    {
      "iss": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
      "sub": "9f8e7d6c-1a2b-3c4d-5e6f-...",
      "aud": "<APP_ID>",
      "oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
      "tid": "<TENANT_ID>",
      "azp": "<IDENTITY_CLIENT_ID>",
      "ver": "2.0",
      "exp": 1775527120
    }
    ```

    `sub` 與 `oid` 是受控識別的物件 ID，`aud` 是受眾應用程式註冊的用戶端 ID，而 `azp` 是受控識別的用戶端 ID（即 `AZURE_CLIENT_ID` 的值）。效期與受控識別路徑不同：`client_credentials` 權杖在 `iat` 與 `exp` 之間預設為隨機的 60 至 90 分鐘範圍，而非 24 小時。
  </Step>
</Steps>

### 設定 Anthropic

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

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

**聯合簽發者：** 在精靈的 **Token issuer** 選擇器中選擇 **v2.0 (login.microsoftonline.com)**。（選擇器預設為 v1；該預設值是為了重複使用仍發出 v1.0 權杖之舊註冊的租用戶而存在。）Entra 會在每個租用戶專屬的簽發者 URL 發佈 OIDC 探索文件，因此請使用探索模式。您建立聯合的每個 Microsoft Entra 租用戶都需要自己的簽發者記錄。

```json
{
  "name": "azure-prod-tenant",
  "issuer_url": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
  "jwks": { "type": "discovery" },
  "max_jwt_lifetime_seconds": 7500
}
```

<Warning>
  Connect workload 精靈的 Microsoft Entra 圖塊在建立簽發者時會將 `max_jwt_lifetime_seconds` 設為 `7500`（略超過 2 小時），足以涵蓋 `client_credentials` 權杖預設 60 到 90 分鐘的效期。租用戶權杖效期原則或「Continuous Access Evaluation」（持續存取評估），即 CAE，可能會延長該效期。如果您解碼後權杖的 `exp` 減去 `iat` 超過 7500 秒，請在 Claude Console 中 [Workload identity](https://platform.claude.com/settings/workload-identity-federation) 頁面的 **Issuers** 分頁編輯簽發者，並將 `max_jwt_lifetime_seconds` 提高到相符的值，否則交換會失敗並回傳不透明的 `401` `authentication_error` 回應（訊息為 `Authentication failed`）。如果您的租用戶也執行來自[使用受控識別](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#use-a-managed-identity)的受控識別工作負載，請使用該章節的 `86400` 值，它可同時涵蓋兩種路徑。
</Warning>

接受的效期越長，代表外洩的 Entra 權杖可被交換的時間越長。若權杖外洩，應對手段是停用聯合規則；而嚴格的 `oid` 比對則從一開始就限制了哪些識別可以交換權杖，如[限定規則範圍](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#scope-your-rule)所述。

**聯合規則：** 比對受控識別的物件 ID 與您的租用戶 ID。對於本指南所設定的 v2.0 權杖，`audience` 值為受眾應用程式註冊的用戶端 ID（來自[註冊權杖受眾](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#register-the-token-audience)的 `<APP_ID>` GUID）。請使用您解碼後權杖中確切的 `aud` 值。

```json
{
  "name": "azure-inference-worker",
  "issuer_id": "fdis_...",
  "match": {
    "audience": "<APP_ID>",
    "claims": {
      "oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
      "tid": "<TENANT_ID>"
    }
  },
  "target": {
    "type": "service_account",
    "service_account_id": "svac_..."
  },
  "workspace_id": "wrkspc_...",
  "oauth_scope": "workspace:developer",
  "token_lifetime_seconds": 600
}
```

`token_lifetime_seconds` 是交換所回傳之 Anthropic 存取權杖的效期，而非 Entra 權杖的效期；SDK 會為您重新整理它。

### 取得並使用權杖

在執行階段，pod 會執行兩段式交換：它將 Kubernetes 投射權杖（`AZURE_FEDERATED_TOKEN_FILE` 所指的檔案）作為聯合 `client_credentials` 斷言傳送到 Entra 的權杖端點，然後在 `POST /v1/oauth/token` 交換所得的 Entra 存取權杖。當您提供 `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`) 時，Claude SDK 會處理第二次交換和重新整理迴圈，如以下範例所示。除了 Java 之外（請參閱該範例中的註解），該選項接受您的 Entra 擷取作為權杖提供者，SDK 會在每次重新整理時再次呼叫它。cURL 分頁展示原始流程。

範例中會出現兩個不同的用戶端 ID。`<APP_ID>` 是來自[註冊權杖受眾](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#register-the-token-audience)的受眾應用程式註冊用戶端 ID；範圍 `api://<APP_ID>/.default` 會向 Entra 請求一個以該受眾為對象的權杖。`$AZURE_CLIENT_ID` 是受控識別的用戶端 ID，由 webhook 注入，用來識別呼叫端。請勿將兩者互相替換。

<Tip>
  如果您的工作負載已經使用 Azure Identity 用戶端程式庫，請從中取得 Entra 權杖（使用範圍 `api://<APP_ID>/.default` 的 `DefaultAzureCredential`），而不是自行執行兩段式交換。請以範例中的方式將其提供給 `identity_token_provider` (typescript: `identityTokenProvider`; php: `identityProvider`; csharp: `IdentityTokenProvider`; go: `option.WithFederationTokenProvider`; java: `IdentityTokenConfig`)。該程式庫會讀取相同的 `AZURE_FEDERATED_TOKEN_FILE`、`AZURE_CLIENT_ID` 和 `AZURE_TENANT_ID` 環境變數，並處理 Entra 交換。
</Tip>

<CodeGroup>
  ```bash cURL
  # 1. 將 Kubernetes 投射的權杖（位於 $AZURE_FEDERATED_TOKEN_FILE）
  #    交換為 Entra 簽發的 JWT。
  ENTRA_JWT=$(curl -sS "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
    -d grant_type=client_credentials \
    -d "client_id=$AZURE_CLIENT_ID" \
    --data-urlencode "scope=api://<APP_ID>/.default" \
    -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
    --data-urlencode "client_assertion@$AZURE_FEDERATED_TOKEN_FILE" \
    | jq -r .access_token)

  # 2. 將 Entra JWT 交換為 Anthropic 存取權杖。
  ACCESS_TOKEN=$(curl -sS https://api.anthropic.com/v1/oauth/token \
    -H "content-type: application/json" \
    -d @- <<JSON | jq -r .access_token
  {
    "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
    "assertion": "$ENTRA_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
  )

  # 3. 呼叫 Claude API。
  curl -sS 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 Azure"}]
    }' | jq -r '.content[] | select(.type == "text") | .text'
  ```

  ```python Python
  import os
  from pathlib import Path

  import anthropic
  import requests
  from anthropic import WorkloadIdentityCredentials


  def fetch_entra_token_via_federation() -> str:
      federated_token = Path(os.environ["AZURE_FEDERATED_TOKEN_FILE"]).read_text()
      response = requests.post(
          f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}/oauth2/v2.0/token",
          data={
              "client_id": os.environ["AZURE_CLIENT_ID"],
              "grant_type": "client_credentials",
              "scope": "api://<APP_ID>/.default",
              "client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
              "client_assertion": federated_token,
          },
          timeout=5,
      )
      response.raise_for_status()
      return response.json()["access_token"]


  client = anthropic.Anthropic(
      credentials=WorkloadIdentityCredentials(
          identity_token_provider=fetch_entra_token_via_federation,
          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 Azure"}],
  )
  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 { readFile } from "node:fs/promises";

  async function fetchEntraTokenViaFederation(): Promise<string> {
    const federatedToken = await readFile(process.env.AZURE_FEDERATED_TOKEN_FILE!, "utf8");
    const response = await fetch(
      `https://login.microsoftonline.com/${process.env.AZURE_TENANT_ID}/oauth2/v2.0/token`,
      {
        method: "POST",
        headers: { "content-type": "application/x-www-form-urlencoded" },
        body: new URLSearchParams({
          client_id: process.env.AZURE_CLIENT_ID!,
          grant_type: "client_credentials",
          scope: "api://<APP_ID>/.default",
          client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
          client_assertion: federatedToken
        })
      }
    );
    const body = (await response.json()) as { access_token: string };
    return body.access_token;
  }

  const client = new Anthropic({
    credentials: oidcFederationProvider({
      identityTokenProvider: fetchEntraTokenViaFederation,
      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 Azure" }]
  });
  for (const block of message.content) {
    if (block.type === "text") {
      console.log(block.text);
    }
  }
  ```

  ```go Go
  package main

  import (
  	"context"
  	"encoding/json"
  	"fmt"
  	"net/http"
  	"net/url"
  	"os"
  	"strings"

  	"github.com/anthropics/anthropic-sdk-go"
  	"github.com/anthropics/anthropic-sdk-go/option"
  )

  func fetchEntraTokenViaFederation(ctx context.Context) (string, error) {
  	federatedToken, err := os.ReadFile(os.Getenv("AZURE_FEDERATED_TOKEN_FILE"))
  	if err != nil {
  		return "", err
  	}
  	form := url.Values{
  		"client_id":             {os.Getenv("AZURE_CLIENT_ID")},
  		"grant_type":            {"client_credentials"},
  		"scope":                 {"api://<APP_ID>/.default"},
  		"client_assertion_type": {"urn:ietf:params:oauth:client-assertion-type:jwt-bearer"},
  		"client_assertion":      {strings.TrimSpace(string(federatedToken))},
  	}
  	tokenURL := "https://login.microsoftonline.com/" + os.Getenv("AZURE_TENANT_ID") + "/oauth2/v2.0/token"
  	req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(form.Encode()))
  	if err != nil {
  		return "", err
  	}
  	req.Header.Set("content-type", "application/x-www-form-urlencoded")
  	resp, err := http.DefaultClient.Do(req)
  	if err != nil {
  		return "", err
  	}
  	defer resp.Body.Close()
  	var body struct {
  		AccessToken string `json:"access_token"`
  	}
  	if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
  		return "", err
  	}
  	return body.AccessToken, nil
  }

  func main() {
  	client := anthropic.NewClient(
  		option.WithFederationTokenProvider(option.IdentityTokenFunc(fetchEntraTokenViaFederation), 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 Azure")),
  		},
  	})
  	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 com.fasterxml.jackson.databind.ObjectMapper;
  import java.net.URI;
  import java.net.URLEncoder;
  import java.net.http.HttpClient;
  import java.net.http.HttpRequest;
  import java.net.http.HttpResponse;
  import java.nio.file.Files;
  import java.nio.file.Path;
  import java.util.Map;
  import java.util.stream.Collectors;
  import static java.nio.charset.StandardCharsets.UTF_8;

  void main() throws Exception {
      var form = Map.of(
                      "client_id", System.getenv("AZURE_CLIENT_ID"),
                      "grant_type", "client_credentials",
                      "scope", "api://<APP_ID>/.default",
                      "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                      "client_assertion", Files.readString(Path.of(System.getenv("AZURE_FEDERATED_TOKEN_FILE"))))
              .entrySet().stream()
              .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
              .collect(Collectors.joining("&"));
      var request = HttpRequest.newBuilder(URI.create(
                      "https://login.microsoftonline.com/" + System.getenv("AZURE_TENANT_ID") + "/oauth2/v2.0/token"))
              .header("content-type", "application/x-www-form-urlencoded")
              .POST(HttpRequest.BodyPublishers.ofString(form))
              .build();

      // Java SDK 接受權杖本身或檔案路徑，而非函式；使用
      // source("value") 時，path(...) 存放權杖。SDK 會在每次重新整理
      // Anthropic 存取權杖時傳送同一個權杖，一旦該權杖
      // 過期，或（若含 jti 宣告）已被使用過，就會失敗。對於長時間執行的
      // 程序，請傳入 source("file")，並在每次重新整理前
      // 將新權杖寫入該檔案。
      var response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString());
      String entraToken = new ObjectMapper().readTree(response.body()).get("access_token").asText();

      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(entraToken)
                                      .build())
                              .build())
                      .build()))
              .build();

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

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

  ```csharp C#
  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 EntraFederationTokenProvider(),
  });
  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 Azure" }],
  });
  foreach (var block in message.Content)
  {
      if (block.Value is TextBlock textBlock)
      {
          Console.WriteLine(textBlock.Text);
      }
  }

  class EntraFederationTokenProvider : IIdentityTokenProvider
  {
      private static readonly HttpClient Http = new();

      public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
      {
          var federatedToken = await File.ReadAllTextAsync(
              Environment.GetEnvironmentVariable("AZURE_FEDERATED_TOKEN_FILE")!, ct);
          var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
          var form = new FormUrlEncodedContent(new Dictionary<string, string>
          {
              ["client_id"] = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")!,
              ["grant_type"] = "client_credentials",
              ["scope"] = "api://<APP_ID>/.default",
              ["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
              ["client_assertion"] = federatedToken,
          });
          var response = await Http.PostAsync(
              $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", form, ct);
          response.EnsureSuccessStatusCode();
          using var json = await JsonDocument.ParseAsync(
              await response.Content.ReadAsStreamAsync(ct), default, ct);
          return json.RootElement.GetProperty("access_token").GetString()!;
      }
  }
  ```

  ```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;

  class EntraIdentityTokenViaFederation implements IdentityTokenProvider
  {
      public function getIdentityToken(): string
      {
          $ch = curl_init('https://login.microsoftonline.com/' . getenv('AZURE_TENANT_ID') . '/oauth2/v2.0/token');
          curl_setopt_array($ch, [
              CURLOPT_RETURNTRANSFER => true,
              CURLOPT_POSTFIELDS => http_build_query([
                  'client_id' => getenv('AZURE_CLIENT_ID'),
                  'grant_type' => 'client_credentials',
                  'scope' => 'api://<APP_ID>/.default',
                  'client_assertion_type' => 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
                  'client_assertion' => file_get_contents(getenv('AZURE_FEDERATED_TOKEN_FILE')),
              ]),
          ]);
          $body = json_decode(curl_exec($ch), true);
          curl_close($ch);
          return $body['access_token'];
      }
  }

  $client = new Client(
      credentials: new CredentialResult(
          provider: new TokenCache(new WorkloadIdentityCredentials(
              identityProvider: new EntraIdentityTokenViaFederation(),
              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 Azure']],
  );
  $textBlock = array_find($message->content, static fn ($block): bool => $block->type === 'text');
  echo $textBlock->text, PHP_EOL;
  ```

  ```ruby Ruby
  require "anthropic"
  require "json"
  require "net/http"

  def fetch_entra_token_via_federation
    tenant_id = ENV.fetch("AZURE_TENANT_ID")
    federated_token = File.read(ENV.fetch("AZURE_FEDERATED_TOKEN_FILE"))
    response = Net::HTTP.post_form(
      URI("https://login.microsoftonline.com/#{tenant_id}/oauth2/v2.0/token"),
      "client_id" => ENV.fetch("AZURE_CLIENT_ID"),
      "grant_type" => "client_credentials",
      "scope" => "api://<APP_ID>/.default",
      "client_assertion_type" => "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
      "client_assertion" => federated_token
    )
    JSON.parse(response.body).fetch("access_token")
  end

  client = Anthropic::Client.new(
    credentials: Anthropic::WorkloadIdentityCredentials.new(
      identity_token_provider: -> { fetch_entra_token_via_federation },
      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 Azure"}]
  )
  puts message.content.find { it.type == :text }.text
  ```

  ```bash CLI
  # 1. 將 Kubernetes 投射的權杖交換為 Entra 核發的存取
  # 權杖，並寫入 CLI 可讀取的暫存檔案。
  ANTHROPIC_IDENTITY_TOKEN_FILE=$(mktemp)
  trap 'rm -f "$ANTHROPIC_IDENTITY_TOKEN_FILE"' EXIT
  curl -sS "https://login.microsoftonline.com/$AZURE_TENANT_ID/oauth2/v2.0/token" \
    -d client_id="$AZURE_CLIENT_ID" \
    -d grant_type=client_credentials \
    --data-urlencode "scope=api://<APP_ID>/.default" \
    -d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
    --data-urlencode client_assertion@"$AZURE_FEDERATED_TOKEN_FILE" \
    | jq -r .access_token > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
  export ANTHROPIC_IDENTITY_TOKEN_FILE

  # 2. 呼叫 Claude API。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 Azure"}'
  ```
</CodeGroup>

### 驗證設定

在已加上標籤的 pod 內執行[取得並使用權杖](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#acquire-and-use-the-token-2)中所示的 cURL 交換，並確認 `POST /v1/oauth/token` 回傳 `200`，其中包含以 `sk-ant-oat01-` 開頭的 `access_token`，以及以秒為單位的 `expires_in` 值。如果交換失敗並回傳不透明的 `401` `authentication_error` 回應（訊息為 `Authentication failed`），請在 Claude Console 的[驗證歷程記錄頁面](https://platform.claude.com/settings/workload-identity-federation?tab=history)查看拒絕原因，然後解碼步驟 1 中 Entra 簽發的權杖（指令請參閱[疑難排解失敗的交換](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference#troubleshoot-a-failed-exchange)），並檢查最常見的 Azure 端原因：

* **簽發者不符：** 已註冊的 `issuer_url` 必須與權杖的 `iss` 宣告完全相符。v2.0 權杖帶有 `https://login.microsoftonline.com/<TENANT_ID>/v2.0`；如果解碼後的 `ver` 宣告為 `1.0`，請參閱[如果您的權杖是 v1.0](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0)。
* **權杖效期：** 如果租用戶權杖效期原則或 CAE 將 `client_credentials` 權杖延長超過 7500 秒，請依[設定 Anthropic](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#configure-anthropic-2) 所述提高簽發者的 `max_jwt_lifetime_seconds`。
* **受眾不符：** 規則的 `audience` 必須與權杖的 `aud` 完全相等：對於本指南所設定的 v2.0 權杖，即為受眾應用程式註冊的用戶端 ID。
* **宣告名稱不符：** 比對權杖未攜帶之宣告的規則永遠不會通過。v1.0 權杖將用戶端 ID 放在 `appid` 而非 `azp`；請參閱[如果您的權杖是 v1.0](https://platform.claude.com/docs/zh-TW/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0)。

## 如果您的權杖是 v1.0

本指南以 `api.requestedAccessTokenVersion: 2` 設定受眾應用程式註冊，因此所顯示的每個權杖都是 v2.0。如果您重複使用未設定 `requestedAccessTokenVersion` 的既有註冊，Entra 會改為簽發 v1.0 權杖。請解碼一個範例權杖並檢查其 `ver` 宣告；如果是 `1.0`，有四件事會改變：

* **簽發者：** `iss` 宣告為 `https://sts.windows.net/<TENANT_ID>/`，而非 `https://login.microsoftonline.com/<TENANT_ID>/v2.0`。請完全依照您權杖 `iss` 宣告所帶的內容註冊簽發者 URL。這兩個 URL 共用相同的 JWKS，因此探索模式對兩者皆適用。
* **精靈選擇器：** 在 Connect workload 精靈的 **Token issuer** 選擇器中選擇 **v1 (sts.windows.net)**，而非 **v2.0 (login.microsoftonline.com)**。
* **受眾：** `aud` 宣告是您作為 `resource` 傳入的識別碼 URI（例如 `api://<APP_ID>`），而非註冊的用戶端 ID。請將聯合規則的 `audience` 設為您解碼後權杖中確切的 `aud` 值。
* **用戶端 ID 宣告：** 呼叫端識別的用戶端 ID 出現在 `appid`，而非 `azp`。這兩個宣告永遠不會出現在同一個權杖中，因此比對 `azp` 的規則對 v1.0 權杖永遠不會通過。

`oid`、`sub` 與 `tid` 宣告在兩個版本中帶有相同的值，因此本指南的其餘部分無需更動即可適用。

## 限定規則範圍

聯合規則除了（或取代）`claims` 對應之外，還可以使用 `subject_prefix` 比對權杖的主體；各欄位如何組合請參閱[規則比對語意](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference#rule-matching-semantics)。這些識別的 Entra `sub` 值是固定長度的標準 GUID，因此包含完整 36 字元物件 ID 的 `subject_prefix` 只會比對到該主體；這是 Entra 主體格式的特性，而非 `subject_prefix` 的一般特性。

<Warning>
  您租用戶中的每個識別都可以為已註冊的受眾請求權杖， 因此僅憑 `audience` 與 `tid` 無法識別特定的工作負載。若規則 省略了 `oid`（或 `azp`/`appid`）比對，或使用萬用字元或 部分 GUID 的 `subject_prefix`，就會授權租用戶中的每個受控識別與服務 主體。
</Warning>

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

* **以確切值比對 `oid`：** 將 `claims.oid` 設為受控識別的完整物件 ID。設為該完整物件 ID 的 `subject_prefix` 與之等效（Console 精靈會同時設定兩者）；切勿使用萬用字元或部分 GUID 的 `subject_prefix`，那會比對到比您預期更多的識別。
* **鎖定 `tid` 作為縱深防禦：** 簽發者 URL 已經鎖定了您的租用戶，但加入 `claims.tid` 可防範日後編輯簽發者記錄時的設定漂移。
* **鎖定受眾：** 將 `audience` 設為您解碼後權杖中確切的 `aud` 值，以拒絕為其他應用程式鑄造的權杖。
* **為每個受控識別使用獨立的規則：** 為每個識別建立一條規則，而非一條授權多個識別的規則，如此您便能撤銷單一工作負載的存取權而不影響其他工作負載。

## 後續步驟

* 在[工作負載識別聯合](https://platform.claude.com/docs/zh-TW/manage-claude/workload-identity-federation)中檢閱完整的設定模型。
* 參閱 AWS、Google Cloud、GitHub Actions 與 Kubernetes 的[提供者指南](https://platform.claude.com/docs/zh-TW/manage-claude/workload-identity-federation#identity-providers)。
* 關於環境變數、設定檔與認證優先順序，請參閱 [WIF 參考](https://platform.claude.com/docs/zh-TW/manage-claude/wif-reference)。
