こんにちは、GW中にちょうど Claudeの Workload Identity Federation がリリースされたみたいなので、検証してみました。
選択できる Identity Provider にはAWSやGoogleCloudなどいろいろありましたが、今回は一番手軽そうなGitHub ActionsをIdentity Providerにして検証ました。
具体的には、リポジトリにPRが作成されるとWIFによって呼び出されたClaudeがそのPRのdiffをレビューするよくあるワークフローを実装してみました。
TL;DR
- Claude Console でワークロードIDの発行者,サービスアカウント,ルールを設定し、GitHubActionsのワークフローのyamlファイルでOIDC連携するだけ
ANTHROPIC_API_KEYをリポジトリのSecretsに置かなくてよくなる- 仕組みは AWSの OIDC認証と同じような感じ
Workload Identity Federation(通称WIF)とは
Workload Identity Federation(通称WIF) はざっくりいうと、ClaudeのAPIキーをGitHub ActionsのSecretsに置いておかなくても Identity Provider が発行するOIDCトークンと短命なAnthropicアクセストークンを引き換えてClaude APIを呼び出せるようになる機能 です。
Identity ProviderにGitHub Actionsを選択した場合、こんな感じの順番で処理が行われます↓
GitHub Actions GitHub OIDC Anthropic Claude API
workflow Provider /v1/oauth/token /v1/messages
│ │ │ │
│── ①JWT要求 ─────▶│ │ │
│◀── ②署名JWT ─────│ │ │
│ │ │
│── ③JWTを提示 ──────────────────────▶│ │
│ (JWKSで署名検証) │ │
│ (Federation Rule) │ │
│◀── ④sk-ant-oat01-... ──────────────│ │
│ │ │
│── ⑤Authorization: Bearer ... ─────────────────────────────▶│
│◀── ⑥レスポンス ──────────────────────────────────────────────│
セットアップ手順
以下の権限を持つ必要があります。
- Anthropic Organization の Admin 権限
- GitHub リポジトリの workflow を編集できる権限
1. Claude Console 側
1-1. ワークロードの発行者(issuer)を登録
Claude Console > 組織の設定 にアクセスし、
ワークロード > 発行者を登録 にアクセスします。
- 名前: 任意。
github-actionsなどわかりやすい名前がよさそう - 発行者URL:
https://token.actions.githubusercontent.com - JWKSソース: ディスカバリー(GitHubは公開discoveryを持っているのでそのままでOK)
- CA証明書: 不要
- シングルユーズトークン: トークンを再利用する具体的なケースがいまいち分からないが安全に倒すなら一旦ONにしておいた方が良さそう
登録後以下のように一覧に表示されます。
1-2. サービスアカウント を作成
サービスアカウント > サービスアカウントを作成 にアクセスします。
- 名前: 任意。わかりやすい名前がよさそう
- 説明: 任意。わかりやすい名前がよさそう
登録後これも一覧に表示されます。後で使うのでsvac_... というIDを控えておきましょう。
1-3. ワークロードのルール を作成
ワークロード > 新しいルール にアクセスします。
- 名前: 任意。わかりやすい名前がよさそう
- 発行者: 1-1で作った発行者を選択
- 一致設定: これがめっちゃ重要です
- 手軽に設定するなら パターンマッチ を選択
- 件名パターン: 一致させる
subのパターンを設定します。GitHub Actionsが払い出すOIDCトークンのsubの形式はワークフローの種類によるので、その形式に合わせた最適な設定が必要です。- 具体的な値はAppendixに記載してます
- 今回はPRに反応するワークフローを作るので
repo:<owner>/<repo>:pull_requestを指定
- 件名パターン: 一致させる
- 想定オーディエンス: 不要
- 追加のクレーム条件(任意): これは設定した方が良いです。今回はGitHubのPR作成がトリガーになりますが、fork先リポジトリのPRからのリクエストをブロックするために、
repository_ownerクレームに<owner>を指定しておく必要があります。- repository_owner: リポジトリのオーナー名、この例の場合は
diggymo - repository: リポジトリ名、この例の場合は
diggymo/cc-plugin
- repository_owner: リポジトリのオーナー名、この例の場合は
- 手軽に設定するなら パターンマッチ を選択
- サービスアカウント: 1-2で作成したサービスアカウントを選択
- すべてのワークスペースで有効にする: 必要に応じて(あまり詳しくない)
- トークンの有効期限: デフォルトは10分ですが調整できます。短ければ短いほど侵害されるリスクが減るのでできるだけ短い値を設定しましょう。今回の例だと5分もあれば十分なので5分を設定しました。
登録後これも一覧に表示されます。後で使うのでfdlr_... というIDを控えておきましょう。
Claude Console側での設定は以上です。
2. GitHub Actions 側
2-1. ワークフローを設定する
ワークフローを設定します。
実際に動作したyamlファイルはこちらから参照できます。
https://github.com/diggymo/cc-plugin/blob/main/.github/workflows/claude-plugin-review.yml
name: Claude Plugin Review
on:
pull_request:
types: [opened, synchronize, reopened]
permissions:
id-token: write # WIFの設定に必要です
contents: read # WIFの設定に必要です
pull-requests: write # これはレビュー結果をPRにコメントするためだけの権限です。WIFには不要です
jobs:
review:
runs-on: ubuntu-latest
# GitHub Actions の Variables に登録しておくと他ワークフローで再利用できて便利です
env:
# 1-3で作ったFederation RuleのID、fdrl_... という形式
ANTHROPIC_FEDERATION_RULE_ID: fdrl_01QWMfa6kLk7z6kq4nhitjHV
# Anthropic Organization ID、Claude Console の Organization settingsから確認できる
ANTHROPIC_ORGANIZATION_ID: db2294e8-5d54-4d31-af31-171bc972fa81
# 1-2で作ったService AccountのID、svac_... という形式
ANTHROPIC_SERVICE_ACCOUNT_ID: svac_01Jo5cZmDB6bHiW8U8sxEGwi
# ワークフロー内で一時的にJWTを保存するファイルパス
ANTHROPIC_IDENTITY_TOKEN_FILE: /tmp/gha-jwt
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
MODEL: claude-sonnet-4-6
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
# GitHub OIDCトークンの取得とAnthropicアクセストークンへの交換
- name: Fetch GitHub OIDC token
run: |
curl -sS -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"$ACTIONS_ID_TOKEN_REQUEST_URL&audience=https://api.anthropic.com" \
| jq -r .value > "$ANTHROPIC_IDENTITY_TOKEN_FILE"
# 取得したJWTをAnthropicのアクセストークンに交換
- name: Exchange JWT for Anthropic access token
run: |
JWT=$(cat /tmp/gha-jwt)
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"
}
JSON
)
ACCESS_TOKEN=$(echo "$RESPONSE" | jq -r .access_token)
# 取得できているかprefix確認
echo "Acquired access token (prefix): ${ACCESS_TOKEN:0:5}..."
# 以降のステップでアクセスできるよう環境変数にセット
echo "::add-mask::$ACCESS_TOKEN"
echo "ANTHROPIC_ACCESS_TOKEN=$ACCESS_TOKEN" >> "$GITHUB_ENV"
# `ANTHROPIC_ACCESS_TOKEN` を使ってClaude APIを呼び出すステップの例
- name: Review with Claude
run: |
# もろもろ設定
REQUEST_BODY=$(jq -n \
--arg model "$MODEL" \
--arg content "$USER_CONTENT" \
'{
model: $model,
max_tokens: 4096,
tools: [
{ type: "web_search_20260209", name: "web_search", max_uses: 3 },
{ type: "web_fetch_20260209", name: "web_fetch", max_uses: 5 }
],
messages: [
{ role: "user", content: $content }
]
}')
RESPONSE=$(curl -sS https://api.anthropic.com/v1/messages \
-H "authorization: Bearer $ANTHROPIC_ACCESS_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$REQUEST_BODY")
# text タイプの content ブロックを連結
echo "$RESPONSE" | jq -r '.content // [] | map(select(.type == "text") | .text) | join("\n")' \
> /tmp/review.md
echo "----- Claude review -----"
cat /tmp/review.md
echo "-------------------------"
- name: Post review comment
run: gh pr comment "$PR_NUMBER" --body-file /tmp/review.md
ANTHROPIC_ORGANIZATION_IDについては、組織の設定画面に記載されています。以下の例だとdb2294eから始まる文字列です
GitHub Actions側での設定は以上です
3. 動作確認
PRを作ってワークフローを実行してみましたが、無事にClaudeが呼び出されてレビューコメントが投稿されました。
検証するために雑に作ったPRなのでかなり指摘されてます🥺
うまくいかない時
Claude Consoleの ワークフローID > 認証イベントから、イベントログが閲覧できるのでうまくいかないときはまずここを確認して原因を調べた方が良いです。
私の場合は、最初設定した際にルールに設定したclaimのマッチングルールが間違っていたため、以下のようなエラーになっていました。はっきりとしたエラー内容が記載されてるので原因の特定は比較的簡単でした。
"status": {
"detail": "assertion does not satisfy rule match: subject does not satisfy subject_prefix: sub (hash=a9d1f427bf4f896c) does not match prefix \"repo:morimorikochan/claude-notify-plugin:pull_request\"",
"outcome": "failure",
"reason": "match_subject_prefix"
},
うまくいった場合はこんな感じです。
JSONは以下のようになっていました。
event_data.oidc_token.claimsを見ると actor_id(PRを実行したユーザーのID) や event_name(トリガーとなったイベント名) なども入っているのでこれらを使ってより厳密なルールを設定することも可能そうです。
{
"actor": {
"audience": [
"https://api.anthropic.com"
],
"ip_address": "172.183.134.178",
"issuer": "https://token.actions.githubusercontent.com",
"subject": "repo:diggymo/cc-plugin:pull_request",
"type": "federated_identity_actor",
"user_agent": "curl/8.5.0"
},
"created_at": "2026-05-05T10:03:31.881038Z",
"event_data": {
"federation_rule_id": "fdrl_01QWMfa6kLk7z6kq4nhitjHV",
"issuer_id": "fdis_01QAY7ynYzSouwvKwNYQzoPB",
"oidc_token": {
"claims": {
"actor": "diggymo",
"actor_id": "24623083",
"aud": "https://api.anthropic.com",
"base_ref": "main",
"check_run_id": "74390810760",
"event_name": "pull_request",
"exp": 1777975711,
"head_ref": "feat/pr-description",
"iat": 1777975411,
"iss": "https://token.actions.githubusercontent.com",
"job_workflow_ref": "diggymo/cc-plugin/.github/workflows/claude-plugin-review.yml@refs/pull/3/merge",
"job_workflow_sha": "173206af73c3af8dd880bfcdfc20c8e807b23b21",
"jti": "4cf4801e-1e19-410b-9371-cbbc077c9c18",
"nbf": 1777975111,
"ref": "refs/pull/3/merge",
"ref_protected": "false",
"ref_type": "branch",
"repository": "diggymo/cc-plugin",
"repository_id": "1175104230",
"repository_owner": "diggymo",
"repository_owner_id": "24623083",
"repository_visibility": "public",
"run_attempt": "1",
"run_id": "25370016512",
"run_number": "3",
"runner_environment": "github-hosted",
"sha": "173206af73c3af8dd880bfcdfc20c8e807b23b21",
"sub": "repo:diggymo/cc-plugin:pull_request",
"workflow": "Claude Plugin Review",
"workflow_ref": "diggymo/cc-plugin/.github/workflows/claude-plugin-review.yml@refs/pull/3/merge",
"workflow_sha": "173206af73c3af8dd880bfcdfc20c8e807b23b21"
},
"jti": "4cf4801e-1e19-410b-9371-cbbc077c9c18"
},
"requested_service_account_id": "svac_01Jo5cZmDB6bHiW8U8sxEGwi"
},
"id": "activity_01XmEVAU2uPriE94ZMpbvg82",
"organization_id": "org_01U4TwuxQRsocnVEpSRVjqjn",
"request_id": "req_011CajEP1Wp2FLV5g7p1abjh",
"resources": [
{
"id": "svac_01Jo5cZmDB6bHiW8U8sxEGwi",
"type": "service_account"
}
],
"status": {
"outcome": "success"
},
"type": "platform_federated_authentication"
}
GitHub Actions のOIDCトークンの sub に入る値
1-3でルールに設定する sub のパターンは、ワークフローのトリガーや設定によって変わります。
GitHub公式リファレンス によると、デフォルトの sub は以下の優先順位で決まります(上から評価され、最初にマッチしたものが採用されます)。
| # | 条件 | sub のフォーマット |
例 |
|---|---|---|---|
| 1 | ジョブが environment を参照している |
repo:<owner>/<repo>:environment:<environment> |
repo:octo-org/octo-repo:environment:Production |
| 2 | pull_request イベントがトリガー(環境参照なし) |
repo:<owner>/<repo>:pull_request |
repo:octo-org/octo-repo:pull_request |
| 3 | ブランチへのpush等(上記以外) | repo:<owner>/<repo>:ref:refs/heads/<branch> |
repo:octo-org/octo-repo:ref:refs/heads/main |
| 4 | タグへのpush(上記以外) | repo:<owner>/<repo>:ref:refs/tags/<tag> |
repo:octo-org/octo-repo:ref:refs/tags/v1.0.0 |
補足
- 値の中に
:が含まれる場合(例:Production:V1という environment 名)はURLエンコードされProduction%3AV1となります sub以外のクレーム(repository_owner,repository,ref,event_name,actorなど)は本記事のJSONログにある通りトークンに含まれているので、Federation Ruleの「追加のクレーム条件」で組み合わせて検証することで、subだけでは表現できない細かい制約をかけられます
参考資料
- Workload Identity Federation - Claude API Docs
- Use WIF with GitHub Actions - Claude API Docs
- OpenID Connect reference - GitHub Docs