GitHub にうっかり API キーを含んだファイルを push してしまい、青ざめた経験はないでしょうか。私自身、個人開発で複数のリポジトリを回していた頃に一度やってしまい、慌てて鍵を失効させて回りました。幸い実害は出ませんでしたが、「鍵という長寿命の文字列が、コードやログや CI のどこかに必ず残り続ける」という構造そのものが怖いのだ、と腹の底で理解した出来事でした。
2026 年 6 月、Claude Platform で Workload Identity Federation(以下 WIF)が一般提供になりました。要点を一言でいえば、sk-ant-... という長寿命の静的キーを完全に手放し、数分で失効する短命トークンを実行時に都度発行してもらう方式です。鍵を「保管する」前提が消えるので、漏洩・ローテーション・棚卸しといった面倒事の多くが土台から無くなります。Dolice Labs で複数サイトの自動投稿を一人で回している身からすると、地味ですが効き目の大きい変化でした。ここでは仕組みと、CI・本番へ実際に落とし込む手順を、つまずきやすい点まで含めて整理していきます。
静的 API キーが構造的に抱えるリスク
静的キーの厄介さは、性能でも価格でもなく「期限が無いこと」にあります。一度発行した sk-ant-... は、あなたが明示的に失効させない限り永遠に有効です。つまり、その文字列が一瞬でも露出した瞬間から、失効操作を終えるまでの全期間が攻撃可能な窓になります。
しかも露出経路は驚くほど多彩です。コミット履歴、CI のログ出力、シェルの履歴ファイル、コンテナイメージのレイヤ、エラー監視に飛んだスタックトレース。どれか一つでもすり抜ければ、その鍵は「持っている人が誰でも本人になりすませる」状態になります。鍵そのものが資格情報である以上、これは仕組み上避けられません。関連する応急対応はコミットした API キーの緊急対応手順 にもまとめていますが、本質的にはキーを持たないのがいちばん強い対策です。
「鍵を持つ」から「都度発行される」へ
WIF の発想は、資格情報の持ち方を反転させます。従来の API キーは、それ自体が資格情報でした。WIF では、サービスアカウント という主体に対して、必要なときだけトークンが発行されます。鍵を「持つ」のではなく、鍵を「都度ミントしてもらう」わけです。
流れはおおまかに三段です。
すでに運用している ID プロバイダ(IdP)が、ワークロードに対して署名付きの OIDC トークン(JWT)を発行します。多くの環境では、これは自動で手に入ります。Kubernetes の projected service-account token、Google Cloud のメタデータサーバー、Azure の IMDS、GitHub Actions の OIDC エンドポイントなどです。
SDK がその JWT を Claude のトークンエンドポイントへ提示し、Anthropic 側で署名と各クレームを検証したうえで、短命の Anthropic アクセストークンに交換します。
SDK はそのトークンを毎リクエストに付与し、失効前に自動で再交換します。アプリ側のコードは api_key を一切渡さずにいつも通り呼ぶだけです。
ここで重要なのは、SDK が交換と更新のループを肩代わりしてくれる点です。開発者が触るのは「どのルールで、どの IdP のトークンを交換するか」という設定だけになります。
三つの登録物:サービスアカウント・発行者・ルール
WIF を使う前に、Claude Console で三つのリソースを作っておきます。三つ揃って初めて「発行者 X が署名し、クレームが Y のトークンは、サービスアカウント Z として振る舞ってよい」という関係が成立します。
サービスアカウント(svac_...) は、組織内の人間ではない ID です。フェデレーションで発行されたトークンが「誰として」振る舞うかを表します。メールもパスワードも Console ログインも持ちません。ワークスペースのメンバーに追加されたときだけ、そのワークスペースで有効になります。発行されたトークンは、API キーと同様にそのワークスペースのレート制限と利用集計に従います。
フェデレーション発行者(fdis_...) は、OIDC の IdP を組織に登録するものです。「この発行者が署名した JWT は、うちの組織のワークロード ID を主張してよい」と宣言します。設定の核は、JWT の iss クレームと完全一致する Issuer URL と、署名検証用の公開鍵をどう取得するか(多くの IdP は discovery で済みます)の二点です。
フェデレーションルール(fdrl_...) は、発行者とサービスアカウントを橋渡しします。「発行者 X の JWT が、サブジェクトやクレームの条件 Y を満たしたら、サービスアカウント Z 向けにスコープ S のトークンを発行する」という対応づけです。サブジェクトの前方一致、監査対象の audience、クレームの完全一致、複雑な条件を書くための CEL 式などで絞り込めます。ここを広く取りすぎると意図より多くの権限を与えてしまうので、IdP のクレームが許す限り具体的に書くのが鉄則です。
ルールは ID で評価されます。クライアントは交換リクエストでどのルールを使うかを明示し、Anthropic はその JWT がルールの条件を満たすかを検証します。暗黙のルール探索はありません。
トークンを交換して短命トークンを受け取る
交換そのものは標準的な OAuth です。ワークロードは IdP の JWT を POST /v1/oauth/token に対して RFC 7523 の jwt-bearer グラントで提示します。Anthropic は登録済みの JWKS で署名を検証し、exp・nbf・iat を確認し、指定されたルールの条件と突き合わせます。返ってくるのは標準的な OAuth 2.0 のレスポンスで、sk-ant-oat01-... という短命トークンが含まれます。
{
"access_token" : "sk-ant-oat01-xxxxxxxx..." ,
"token_type" : "Bearer" ,
"expires_in" : 3600 ,
"scope" : "workspace:developer"
}
SDK を使う場合は、この交換を自分で書く必要はありません。本番で推奨されるのは「引数なしでクライアントを構築し、環境変数で環境ごとに差し込む」形です。同じコンテナイメージをどの環境にも配り、ANTHROPIC_FEDERATION_RULE_ID などを注入するだけで動かせます。
from anthropic import Anthropic, WorkloadIdentityCredentials, IdentityTokenFile
# 明示指定する場合。本番では引数なし構築 + 環境変数注入が推奨
client = Anthropic(
credentials = WorkloadIdentityCredentials(
identity_token_provider = IdentityTokenFile(
"/var/run/secrets/anthropic.com/token"
),
federation_rule_id = "fdrl_..." ,
organization_id = "00000000-0000-0000-0000-000000000000" ,
service_account_id = "svac_..." ,
workspace_id = "wrkspc_..." ,
),
)
message = client.messages.create(
model = "claude-sonnet-4-6" ,
max_tokens = 1024 ,
messages = [{ "role" : "user" , "content" : "Hello, Claude" }],
)
print (message.content[ 0 ].text)
api_key がどこにも出てこない点に注目してください。コードからもイメージからも、長寿命の秘密が消えています。
GitHub Actions から鍵なしで Claude API を呼ぶ
個人開発で WIF の恩恵がいちばん分かりやすいのは CI です。私は複数サイトのコンテンツ生成を GitHub Actions と定期実行で回していますが、ここに静的キーを置くのが長年の弱点でした。リポジトリシークレットに鍵を入れる以上、その鍵はワークフローログやサードパーティ Action から漏れうるからです。
GitHub Actions は OIDC トークンを発行できるので、WIF の発行者として登録すれば、シークレットに鍵を一切置かずに認証できます。ワークフロー側で必要なのは、id-token: write 権限と、OIDC トークンをファイルへ書き出してから環境変数で SDK に渡す数行だけです。
permissions :
id-token : write # OIDC トークン発行に必須
contents : read
jobs :
generate :
runs-on : ubuntu-latest
env :
ANTHROPIC_ORGANIZATION_ID : ${{ vars.ANTHROPIC_ORG_ID }}
ANTHROPIC_SERVICE_ACCOUNT_ID : ${{ vars.ANTHROPIC_SVAC_ID }}
ANTHROPIC_WORKSPACE_ID : ${{ vars.ANTHROPIC_WORKSPACE_ID }}
ANTHROPIC_FEDERATION_RULE_ID : ${{ vars.ANTHROPIC_RULE_ID }}
ANTHROPIC_IDENTITY_TOKEN_FILE : ${{ runner.temp }}/anthropic_oidc.jwt
steps :
- uses : actions/checkout@v4
- name : Mint OIDC token for Anthropic
run : |
# audience はルールの match.audience と一致させる
TOKEN=$(curl -sS \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=anthropic" \
-H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
| python3 -c 'import sys,json; print(json.load(sys.stdin)["value"])')
printf '%s' "$TOKEN" > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
- run : pip install anthropic
- run : python scripts/generate_article.py
設定値はすべて vars(公開しても害のない識別子)であり、秘密は一つもありません。svac_... や fdrl_... といった ID は、それ単体では何の権限も持たないからです。鍵という資産が CI から消えるだけで、棚卸しの心理的な重さがずいぶん軽くなりました。GitHub Actions と Claude を連携させる全体像はGitHub Actions と Claude API の自動化構成 も併せて参照してください。
移行で必ず踏む落とし穴:API キーが静かに勝つ
ここが本記事でいちばんお伝えしたい点です。SDK の資格情報解決には明確な優先順位があり、五段階で評価されます。コンストラクタ引数、次に ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN、次に明示的な ANTHROPIC_PROFILE、次にフェデレーション系の環境変数、最後に暗黙のアクティブプロファイル、という順です。最初に資格情報を返した段が勝ちます。
問題は、ANTHROPIC_API_KEY がフェデレーション系より上 に位置することです。つまり、環境のどこかに古い鍵が残っていると、WIF を完璧に設定してもその鍵が黙ってフェデレーションを上書きします。エラーも警告も出ません。「設定したのに使われていない」という最も気づきにくい失敗が、ここで起きます。
移行を無停止で進める手順は次の通りです。
フェデレーションを並行で構成する。 既存の ANTHROPIC_API_KEY はそのまま残し、ルールがワークロードのトークンに正しく一致することを確認します。
どの資格情報が勝っているかを確かめる。 ワークロード内で ant auth status を実行します。この段階では優先順位上、まだ API キーが勝ちます。
ANTHROPIC_API_KEY を注入箇所すべてから外す。 CI シークレット、コンテナ環境、シェルプロファイルから消し、再度 ant auth status でフェデレーション側が選ばれていることを確認します。
API キーを失効させる。 ワークロードがフェデレーショントークンで動いていることを確認できたら、Console で鍵を削除します。
# どの段の資格情報が選ばれているかを必ず確認する
$ ant auth status
# 出力例:
# Resolved credential source: ANTHROPIC_API_KEY (env) ← まだ鍵が勝っている
# ...
# 鍵を外して再確認
$ unset ANTHROPIC_API_KEY
$ ant auth status
# Resolved credential source: workload identity federation (fdrl_...) ← 移行完了
私自身、この優先順位を知らずに「設定したのに反映されない」で半日溶かしかけました。ant auth status を最初に叩く癖をつけるだけで、この罠はほぼ回避できます。
トークンの寿命と更新をどう設計するか
短命トークンである以上、寿命と更新の挙動を理解しておくと運用が安定します。発行されるトークンの寿命は、ルールの token_lifetime_seconds(既定 3600 秒、設定範囲は 60〜86400)と、提示した IdP の JWT の残り寿命の 2 倍とを比べて、小さい方 になります(最低 60 秒の下限つき)。後者の制約は、Anthropic トークンが元となる上流の ID より大きく長生きしないようにするためのものです。
更新は SDK が二段階で面倒を見てくれます。失効の 120 秒前に「予告更新」を試み、もしトークンエンドポイントへ届かなくても、キャッシュ済みトークンはまだ約 90 秒有効なので処理を続けます。失効の 30 秒前には「強制更新」を行い、ここで交換に失敗するとエラーになります。キャッシュが失効に近すぎて安全に使えないからです。
設計上の含意は二つあります。第一に、token_lifetime_seconds を短くしすぎると交換回数が増えます。長時間バッチのような用途では、JWT 側の寿命と相談しつつ、極端に短い値は避けるのが無難です。第二に、SDK は交換のたびに ANTHROPIC_IDENTITY_TOKEN_FILE を読み直すので、Kubernetes の projected token のようにファイルが回転する環境でも、特別な作業なしで新しいトークンを拾ってくれます。長時間ジョブが途中で認証エラーになる典型パターンは、ここを理解していないことが原因になりがちです。
個人開発でどこから取り入れるか
WIF はエンタープライズ向けの重厚な機能に見えますが、個人開発でも入口は軽いです。まず、対話的な開発では ant auth login を使えば、ブラウザで OAuth 認可を済ませるだけで手元から鍵を消せます。ローカルのシェル履歴やドットファイルに sk-ant-... を置かなくて済むので、ここだけでも導入する価値があります。
本番や CI は、影響範囲の小さいワークロードから一つずつ移すのを私はお勧めします。WIF は「鍵を持たない」設計を与えてくれますが、それ自体が完全なセキュリティではありません。署名する IdP が弱ければ全体も弱くなります。IdP 側の条件付きアクセスや監査ログと組み合わせて、多層で守るのが本筋です。複数のコネクタや資格情報を一箇所で束ねる考え方はコネクタ認可を単一の出所に集約する設計 とも地続きで、属人的な鍵運用を少しずつ畳んでいく流れの一部だと感じています。
次の一歩として手頃なのは、手元の開発環境を ant auth login に切り替え、ant auth status で資格情報の出所を一度確認してみることです。鍵が一本でも環境から消えると、運用の景色は思ったより変わります。お読みいただきありがとうございました。