Claude Media
WIF認証とは — ClaudeのAPIをキーなしで使うWorkload Identity Federation

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. 1

    IdPがワークロードにJWTを発行する

    KubernetesのProjected Service Account Token、Google Cloudのメタデータサーバー、Azure IMDS、GitHub ActionsのOIDCエンドポイントなど、多くの環境では発行が自動で起きます。iss が発行元を、sub などが個々のワークロードを示します。

  2. 2

    SDKがJWTをAnthropicのアクセストークンと交換する

    SDKがRFC 7523の jwt-bearer グラントで POST /v1/oauth/token にJWTを送ります。AnthropicはJWTを発行者のJWKSと連合ルールのマッチ条件に照らして検証し、sk-ant-oat01-... 形式の短命トークンを返します。

  3. 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の上限

数値は公式のWIFドキュメントによる

SDKは ANTHROPIC_IDENTITY_TOKEN_FILE を交換のたびに読み直します。ローテーションされるKubernetesのトークンも、そのまま追従します。

ここに落とし穴があります。jti クレームを持つJWTは、発行者ごとに一度しか交換できません。再送は jti_reused で拒否されます。IdPからトークンを自前で取る場合は、リトライのたびに新しいJWTを作ってください。ファイルを読み直す構成でも、中身が更新されていなければ同じ理由で落ちます。再起動したプロセスが交換済みのトークンをもう一度出す場合も同じです。トークン源がそこまで頻繁にローテーションできないときの最後の手段が、発行者の check_jti を無効にすることです。ただし、その発行者に属する全ルールのリプレイ保護が外れます。

APIキーからWIFへ移行する4ステップ

すでにAPIキーで動いているワークロードをダウンタイムなしで切り替える手順です。

手順

APIキーを残したままWIFへ移る

  1. 1

    WIFを並行して設定する

    ConsoleのSettings → Workload identityでConnect workloadを選び、ウィザードを進めます。既存の ANTHROPIC_API_KEY はまだ残します。

  2. 2

    どちらの資格情報が勝つかを確認する

    ワークロード内で ant auth status を実行するか、SDKのデバッグログを見ます。この時点ではまだAPIキーが選ばれます。

  3. 3

    ANTHROPIC_API_KEYをすべての箇所からunsetする

    CIのシークレット、コンテナの環境変数、シェルのプロファイルから外し、ant auth status を再実行して連合経路が選ばれたことを確かめます。

  4. 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の認証履歴から理由を拾えます。

この記事を共有:XはてブLinkedIn