WIF認証とは — ClaudeのAPIをキーなしで使うWorkload Identity Federation
WIF認証は、IdPが発行する短命JWTをAnthropicのトークンと交換し、静的なAPIキーを持たずに認証する仕組みです。向き不向きと移行、失敗時の切り分けまで。
Workload Identity Federationとは何か
WIF認証とは、Claude APIを静的なAPIキーなしで呼び出す方式です。正式名はWorkload Identity Federation(WIF)で、ワークロードが sk-ant-... で始まる静的なAPIキーを持たずに認証します。AWS IAM・Google Cloud・GitHub Actions・Kubernetes・Microsoft Entra ID・Okta・SPIFFEなど、すでに運用しているIdP(アイデンティティプロバイダー)が発行した短命のOIDCトークンを、Anthropicが管理する短命のアクセストークンと交換します。発行・保管・ローテーション・漏えい対応が要る静的シークレットが、原理的に存在しなくなります。
WIFはセキュリティ対策の全部ではありません。連合された認証の強度は、JWTに署名する上流のIdPの強度を超えません。ワークロードIDバインディングや条件付きアクセス、監査ログといったIdP側の統制と組み合わせて初めて多層防御になります。
自分のワークロードにWIFは要るか
入口の条件は2つです。設定できるのは組織のadmin・owner・primary ownerのロールを持つ人で、手元にJWKSを公開しているOIDC対応のIdP(エアギャップ環境ならJWKSを貼り付けられる状態)が必要です。この2つが揃うかどうかで、向き不向きがほぼ決まります。
状況ごとに分けると、次のようになります。
- CI/CDのジョブやKubernetesのPod:実行のたびに使い捨てのアイデンティティを得られるので、WIFがよく合います。GitHub ActionsのOIDCトークンやKubernetesのProjected Tokenをそのまま使えます。
- サービスアカウント単位で権限を切りたい構成:
workspace:developerのスコープにはManaged Agentsとそのセッションも含まれるため、Managed Agentsを動かすワークロードにも同じ連合ルールで権限を渡せます。 - 組織でAPIキー認証を止めたい場合:静的キーを発行しない運用の入口になります。無効化した環境で何が起きるかは組織がAPIキー認証を無効化したときのClaude Codeで扱っています。
- IdPを持たない個人開発や、CI/CDを組んでいない検証環境:連合発行者とルールの登録、JWKSの到達性確認という初期作業が先に立ちます。APIキーのままでも動くので、この作業が割に合うかは運用規模次第です。
- Pythonの非同期アプリでAWSのトークンを使う場合:Python SDKの
identity_token_providerに非同期関数を渡せるようにする要望が、anthropic-sdk-pythonのissue 1901に挙がっています。STSへのネットワーク呼び出しを非同期に書きたいアプリは、この点を確認してから設計に入るのが無難です。
クラウドごとの具体的な連合手順は、AWSとの連携とMicrosoft Entra ID(Azure)との連携にあります。
WIFを構成する3つのリソース
Claude Consoleで連合を組む前に、3種類のリソースを用意します。3つが揃って初めてJWTの交換が成立します。
連合を成立させる3つのリソース
サービスアカウント(svac_...)
組織内の人間ではないアイデンティティです。メールもパスワードもConsoleログインも持ちません。ワークスペースAPIキーが資格情報そのものであるのに対し、サービスアカウントは資格情報を持つ主体です。どのワークロードがどのサービスアカウントとして動いたかを追いやすくなります。
連合発行者(fdis_...)
OIDCのIdPを組織に登録するリソースです。JWTの
issと完全一致する発行者URLと、公開鍵(JWKS)の取得方式を指定します。環境ごとに分けて登録するのが基本です。連合ルール(fdrl_...)
発行者とサービスアカウントを結ぶ条件式です。「この発行者のJWTでクレームがこう見えるなら、このサービスアカウントとして、このスコープのトークンを出す」を表します。
サービスアカウントは組織単位のリソースで、ワークスペースのメンバーに加えたときにそのワークスペースで有効になります。トークン交換のたびに、連合ルールのワークスペースがサービスアカウントのメンバーシップと一致するかをAnthropicが確認します。発行されたトークンは、そのワークスペースのレート制限と利用量帰属に従います。組織の既定ワークスペースには、どのサービスアカウントも暗黙のメンバーとして入ります。
連合発行者のJWKSは、/.well-known/openid-configuration から自動取得する discovery(既定)、URLを直接指定する explicit_url、エアギャップ環境向けに鍵セットを貼る inline の3方式から選びます。発行者URLとJWKSのURLはHTTPS・443番ポート・パブリックDNSホスト名が必須です。IPリテラルは受け付けません。この制約はAnthropicが実際にフェッチするURLだけに適用されます。explicit_url と inline では issuer_url が文字列比較にしか使われません。そのため、内部ホスト名でも登録できます。
連合ルールのマッチ条件は、subject_prefix(末尾 * で前方一致、大文字小文字を区別)、完全一致の audience、完全一致マップの claims、複雑な論理用のCEL式 condition があります。これらを組み合わせます。設定した条件はすべてAND評価になります。subject_prefix・claims・condition のいずれか1つは必須で、audience だけの単独指定はルールごと拒否されます。発行者が出すトークンなら何でも通るルールを作らせないための仕様です。
付与するスコープは、ルール側の上限として働きます。
| スコープ | 届く範囲 |
|---|---|
workspace:developer(既定) | 届く範囲ワークスペースAPIキーと同等の、管理系を除くClaude APIの全エンドポイント |
workspace:inference | 届く範囲Messages・Models・OpenAI互換のチャットエンドポイントだけ |
workspace:manage_tunnels | 届く範囲MCPトンネルのAPI |
org:admin | 届く範囲Admin API全体 |
Messagesを呼ぶだけのワークロードなら workspace:inference に絞れます。範囲外のエンドポイントを呼ぶとHTTP 403が返ります。workspace:developer にはFilesやSkillsも含まれます。MCPトンネルの作成モーダルから作る場合は、スコープが workspace:manage_tunnels に固定されます。リソース単位や読み書き単位のさらに細かいスコープは、今のところありません。実効権限はルールのスコープとサービスアカウントの organization_role(developer か admin)の共通部分になります。そのため、org:admin を渡すルールは admin ロールのサービスアカウントにしか向けられません。
Claude Consoleでの設定手順
Settings → Workload identityのConnect workloadウィザードが、サービスアカウント・連合発行者・連合ルールの3つをまとめて作成します。IdPのタイル(GitHub Actions・AWS・Google Cloud・Microsoft Entra ID・Kubernetes)を選ぶと、発行者URLのパターンとJWTのマッチ用フィールドが自動で入ります。SPIFFEやOktaなど標準準拠の他のOIDCプロバイダーにはCustom OIDCを使います。
ウィザードは oauth_scope=workspace:developer と token_lifetime_seconds=600 を先に入力しますが、変更できます。APIを直接叩いてルールを作るとき、token_lifetime_seconds を省いた場合の既定は3600です。Verify issuerを選ぶと、何も作らずにJWKSを取得・解析できるかだけを確かめられるので、URLの誤入力に早く気づけます。
最後にウィザードが3リソースを作り、15分間トークン交換の成功を待ち受けます。この間にワークロードから交換を発生させてください。時間切れでもリソースは残り、連合ルールの詳細ページから再テストできます。ワークロードには、ルールのID(fdrl_...)、サービスアカウントのID(svac_...)、組織IDを毎回渡します。ルールが複数のワークスペースにまたがるなら、ワークスペースIDも要ります。
トークン交換の中身
交換は3ステップで完結します。
WIFの認証が1リクエストになるまで
- 1
IdPがワークロードにJWTを発行する
KubernetesのProjected Service Account Token、Google Cloudのメタデータサーバー、Azure IMDS、GitHub ActionsのOIDCエンドポイントなど、多くの環境では発行が自動で起きます。
issが発行元を、subなどが個々のワークロードを示します。 - 2
SDKがJWTをAnthropicのアクセストークンと交換する
SDKがRFC 7523の
jwt-bearerグラントでPOST /v1/oauth/tokenにJWTを送ります。AnthropicはJWTを発行者のJWKSと連合ルールのマッチ条件に照らして検証し、sk-ant-oat01-...形式の短命トークンを返します。 - 3
SDKがトークンを毎リクエストに乗せ、失効前に更新する
アプリケーションは
api_keyを指定せずにクライアントを作り、通常どおりAPIを呼ぶだけです。交換と更新のループはSDKが回します。
JWT=$(cat /var/run/secrets/anthropic.com/token)
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": "$JWT",
"federation_rule_id": "fdrl_...",
"organization_id": "00000000-0000-0000-0000-000000000000",
"service_account_id": "svac_...",
"workspace_id": "wrkspc_..."
}
JSON
)
ACCESS_TOKEN=$(jq -r .access_token <<<"$RESPONSE")実際のアプリケーションでは、SDKの WorkloadIdentityCredentials(Python)や oidcFederationProvider(TypeScript)に任せるのが基本です。cURLを直接叩くのはデバッグ時に限ります。本番ではコンテナイメージを共通にします。引数なしで構築し、ANTHROPIC_FEDERATION_RULE_ID・ANTHROPIC_ORGANIZATION_ID・ANTHROPIC_SERVICE_ACCOUNT_ID・ANTHROPIC_WORKSPACE_ID・ANTHROPIC_IDENTITY_TOKEN_FILE を環境ごとに注入します。これが推奨の形です。
設定を置くプロファイル方式もあります。置き場所は $ANTHROPIC_CONFIG_DIR、LinuxとmacOSの ~/.config/anthropic、Windowsの %APPDATA%\Anthropic の順に探されます。LinuxとmacOSでは ~/.config/anthropic/configs/<プロファイル名>.json です。Claude CodeとClaude Agent SDKも同じ解決順序に従うので、ここで作った連合プロファイルはこれらのツールの認証にも使えます。
トークンはいつまで有効か
発行されるトークンの寿命は、ルールの token_lifetime_seconds と、提示したJWTの残り寿命の2倍のうち短いほうです。ただし60秒を下回りません。上流のIDより長生きしすぎないための上限です。
トークン寿命まわりの数字
ルールの寿命設定
60〜86400秒
API既定は3600秒、ウィザードの先入力は600秒
任意の更新を始める時点
失効の120秒前
失敗しても残り約90秒はキャッシュを使い続ける
必須の更新
失効の30秒前
ここで交換に失敗するとエラーになる
JWTの最大サイズ
16 KiB
アサーションとして送るJWTの上限
SDKは ANTHROPIC_IDENTITY_TOKEN_FILE を交換のたびに読み直します。ローテーションされるKubernetesのトークンも、そのまま追従します。
ここに落とし穴があります。jti クレームを持つJWTは、発行者ごとに一度しか交換できません。再送は jti_reused で拒否されます。IdPからトークンを自前で取る場合は、リトライのたびに新しいJWTを作ってください。ファイルを読み直す構成でも、中身が更新されていなければ同じ理由で落ちます。再起動したプロセスが交換済みのトークンをもう一度出す場合も同じです。トークン源がそこまで頻繁にローテーションできないときの最後の手段が、発行者の check_jti を無効にすることです。ただし、その発行者に属する全ルールのリプレイ保護が外れます。
APIキーからWIFへ移行する4ステップ
すでにAPIキーで動いているワークロードをダウンタイムなしで切り替える手順です。
APIキーを残したままWIFへ移る
- 1
WIFを並行して設定する
ConsoleのSettings → Workload identityでConnect workloadを選び、ウィザードを進めます。既存の
ANTHROPIC_API_KEYはまだ残します。 - 2
どちらの資格情報が勝つかを確認する
ワークロード内で
ant auth statusを実行するか、SDKのデバッグログを見ます。この時点ではまだAPIキーが選ばれます。 - 3
ANTHROPIC_API_KEYをすべての箇所からunsetする
CIのシークレット、コンテナの環境変数、シェルのプロファイルから外し、
ant auth statusを再実行して連合経路が選ばれたことを確かめます。 - 4
APIキーを削除する
連合トークンで動いていることを確認してから、ConsoleのSettings → API keysでキー自体を削除します。
なぜ手順2でAPIキーが勝つのかは、認証情報の解決順序にあります。SDKは5段階の優先順位で解決し、最初に見つかった情報源で確定します。
| 優先順位 | 情報源 |
|---|---|
| 1 | 情報源コンストラクタ引数(api_key=等) |
| 2 | 情報源ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN |
| 3 | 情報源ANTHROPIC_PROFILE(明示指定) |
| 4 | 情報源連合用の環境変数(4変数がすべて揃うと有効化) |
| 5 | 情報源アクティブなプロファイル |
最もハマりやすいのは、ANTHROPIC_API_KEY="" のように空文字列でエクスポートしたままにするケースです。空文字列でもスロットは埋まるため、SDKは空キーのままAPIキー経路を選び、連合へフォールバックしません。unset で完全に消す必要があります。プロファイルファイルのスキーマや各階層の詳しい挙動はWIFリファレンスにまとめています。
交換が失敗したときの切り分け
WIFの交換失敗で最初に戸惑うのは、原因が応答に書かれないことです。署名・発行者・マッチ条件のどれで落ちても、返るのは同じ 401 authentication_error で、メッセージは固定の Authentication failed です。ルール設定を探り当てるために使われないよう、意図的にそうなっています。拒否理由はConsoleの認証履歴ページ(Workload identity設定の履歴タブ)に残ります。履歴にはここで評価された発行者とルール、検査されたクレーム、失敗した検証ステップが出ます。まずここを見ると、あとの確認が短くなります。
症状から原因を当てるときは、次の対応が出発点です。
| 症状・履歴の理由 | 確かめること |
|---|---|
401、iss の不一致 | 確かめることJWTの iss が登録した issuer_url とバイト単位で一致するか。末尾スラッシュやスキームの違いでも落ちる |
| 401、マッチ条件の不一致 | 確かめることsubject_prefix は大文字小文字を区別する。audience は aud の要素と完全一致が必要 |
401、workspace_id_required | 確かめることルールが複数ワークスペースにまたがるのに、リクエストが workspace_id を指定していない |
401、jti_reused | 確かめること同じJWTの再送。交換のたびに新しいJWTを用意する |
401、exp 切れ | 確かめること30秒の許容を超えて期限切れ。ホストの時計のずれも疑う |
| 401、履歴に記録なし | 確かめること履歴に出ない401は、ルールID自体が認識されていない場合が多い |
| 交換は成功、APIが403 | 確かめることトークンのスコープがそのエンドポイントに届いていない。ルールの oauth_scope を確認する |
最初のリクエストで FileNotFoundError | 確かめることANTHROPIC_IDENTITY_TOKEN_FILE のパスにファイルがない。SDKは交換時に初めて開く |
JWTの中身を自分の目で確かめるには、次のコマンドでクレームを取り出します。
jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' <<< "$JWT"JWT側の前提も確認しておきます。署名はRSA・ECDSA系の非対称アルゴリズムだけで、HS256 系と none は拒否されます。ヘッダーの kid がJWKSの鍵と対応していること、sub・iat・exp が揃っていることも必須です。exp から iat を引いた寿命は、発行者ごとの上限(既定1時間)を超えられません。時刻の検証には前後30秒の許容があります。
もう1つ、署名鍵のローテーションにも癖があります。IdPが新しい鍵を公開した直後にその鍵で署名を始めると、AnthropicのJWKSキャッシュが更新されるまで最大1分、署名エラーで交換が落ちることがあります。自前でIssuerを運用する場合は、新しい鍵を署名開始の15分以上前にJWKSへ載せ、古い鍵は署名済みトークンが失効するまで残します。inline 方式は自動更新がないので、鍵を替えたら発行者の設定側も手で更新しないと、すべての交換が署名検証で落ちます。
まとめ
WIFの入口は、IdPのOIDCトークンを出せる環境があるかどうかです。ある場合は、既存のAPIキーと並行稼働させながら段階的に切り替えられます。移行が止まる原因はたいてい、解決順序の上位に残ったAPIキーか、空文字列の環境変数のどちらかです。移行後に交換で詰まったら、応答ではなくConsoleの認証履歴から理由を拾えます。