ClaudeのWIFをOktaと連携する手順
Okta service applicationとClient Credentialsフローで、APIキーを使わずClaude APIに認証する設定手順とスコープの絞り方。
OktaとWIFを連携するとは
Workload Identity Federation(WIF)は、sk-ant-... のAPIキーを持たせない認証の仕組みです。既存のIdP(アイデンティティプロバイダー)が発行する短命トークンで、Claude APIに認証します。Oktaには「サービスアプリケーション」という機械向けのアプリ種別があり、OAuth 2.0のclient_credentialsグラントでOIDCアクセストークンを発行します。このOktaトークンをAnthropicのアクセストークンに交換し、v1/messagesを呼ぶまでの設定を、Console側とOkta側の両方から追います。
認証が通るまでの3往復
- 1
ワークロードがOktaに認証する
カスタム認可サーバーのトークンエンドポイントに
client_credentialsで認証し、JWTを受け取ります。 - 2
JWTをAnthropicのトークンに交換する
/v1/oauth/tokenにJWTとfederation ruleのIDなどを渡すと、sk-ant-oat01-で始まる短命アクセストークンが返ります。 - 3
Claude APIを呼ぶ
authorization: Bearerでそのトークンを付け、/v1/messagesなどを呼びます。
Anthropic側のAPIキー(sk-ant-...)を発行・保管・失効させる作業がなくなる点が、APIキー運用との最大の違いです。
前提条件
- OktaのAPI Access Managementが有効な組織アカウント(カスタム認可サーバーの作成に必須)
- Claude Consoleでサービスアカウント・federation issuer・federation ruleを作成できる権限
- Oktaの
/v1/tokenエンドポイントにトークンをリクエストでき、api.anthropic.comに到達できるワークロード
サービスアカウント(svac_...)はAnthropic組織内の人間ではない名前付きID、federation issuer(fdis_...)はOIDCプロバイダーの登録情報です。federation rule(fdrl_...)は「issuerからのJWTがこの条件を満たせば、このサービスアカウントとしてトークンを発行する」という対応表にあたります。3つのリソースをコードから作りたい場合は、WIFをAdmin APIで管理するでそれぞれの作成APIを扱っています。
手順1: Oktaでサービスアプリを作る
- 1
Service app integrationを作る
Okta Admin Consoleで新規アプリ統合をAPI Services(OIDC、machine-to-machine)タイプで作成し、生成されたClient IDを控えます。
- 2
クライアント認証方式を選ぶ
鍵レスにするならPublic key / Private key(
private_key_jwt)を選び、ワークロードの公開JWKを登録します。シークレットを安全に保管できる環境ならクライアントシークレットでも構いません。公式の例を試すときはアプリ側のDPoP要件の無効化が必要になる場合があり、本番構成は組織のセキュリティ要件に従います。 - 3
audienceを設定する
カスタム認可サーバーでaudienceを
https://api.anthropic.comにします。発行されるアクセストークンのaudクレームがこの値になり、Anthropicはこの固定値と照合します。 - 4
scopeを許可する
認可サーバーにサービスアプリがリクエストできるscope(例:
anthropic.access)を最低1つ用意します。許可されたscopeを含まないclient_credentialsリクエストはOktaに拒否されます。 - 5
アクセスポリシーを作る
上の「scopeを許可する」で用意したscopeを、サービスアプリがリクエストできるルールを持つアクセスポリシーを作成します。
- 6
(任意)カスタムクレームを足す
Client ID以外の値で照合したい場合、認可サーバーのClaimsタブでアクセストークンにクレームを追加します。
client_credentialsを使うサービスアプリでは、アクセストークンのsubクレームがアプリケーションのClient IDになります。issは認可サーバーの発行者URLです。
手順2: Anthropic側でissuerとruleを作る
Claude ConsoleでSettings → Workload identityを開き、Connect workloadからCustom OIDCを選びます。ウィザードがissuerの登録、サービスアカウントの作成、federation ruleの作成を順に案内します。同じ内容はAdmin APIから直接送っても作れます。
federation issuer: OktaのカスタムAuthorization Server URLとdiscoveryモードを使います。Anthropicは.well-known/openid-configurationをOktaから読み取り、その中のjwks_uriからJWKSを取得します。
{
"name": "okta-prod",
"issuer_url": "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9",
"jwks": { "type": "discovery" }
}federation rule: Oktaのsubクレーム(サービスアプリのClient ID)に一致させます。カスタムクレームを定義していれば、claimsマップやCEL条件式でそちらを照合対象にできます。
{
"name": "okta-pipeline",
"issuer_id": "fdis_...",
"match": {
"subject_prefix": "0oa1b2c3d4e5f6g7h8i9",
"audience": "https://api.anthropic.com"
},
"target": { "type": "service_account", "service_account_id": "svac_..." },
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}名前が似た2つのscopeを混同しない
この連携にはscopeが2か所に出てきます。Oktaで許可したanthropic.accessと、ruleのoauth_scopeは別物です。
OktaのscopeとAnthropicのoauth_scope
Oktaのscope
Okta認可サーバーに作り、サービスアプリに許可します。Oktaがトークンを発行する条件で、Claude APIで何が呼べるかは決めません。
ruleのoauth_scope
federation ruleに設定します。交換で得たAnthropicトークンが呼べるエンドポイントの上限になります。
oauth_scopeは用途に合わせて選びます。workspace:developerはMessages・Models・Managed Agents・Files・Skillsなど、ワークスペースのAPIキーと同じ範囲です。呼び出しだけのワークロードなら、Messages・Models・OpenAI互換チャットに絞ったworkspace:inferenceが選べます。範囲外のエンドポイントは、交換が成功していても403になります。org:adminはAdmin API用で、対象サービスアカウントのorganization_roleがadminでなければ付けられません。workspace:manage_tunnelsはMCPトンネル専用で、Consoleのトンネル作成画面からruleを作ると、このscopeに固定されます。実効権限は、ruleのscopeとサービスアカウントのロールの積集合です。
手順3: トークンを取得してClaude APIを呼ぶ
AWS・Google Cloud・Kubernetesのようなプラットフォームネイティブなプロバイダーは、トークンを自動で用意します。置き場所はワークロードの実行環境内で、投影ファイルやローカルのメタデータエンドポイントです。Oktaにはこの仕組みがありません。ワークロード側がOktaのトークンエンドポイントを自分で呼び、取得したJWTをAnthropic SDKに渡す必要があります。
# 1. Oktaからアクセストークンを取得(private_key_jwtによるclient_credentials)
OKTA_JWT=$(curl -sS "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9/v1/token" \
-d grant_type=client_credentials \
-d scope=anthropic.access \
-d client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer \
--data-urlencode client_assertion="$SIGNED_CLIENT_ASSERTION" \
| jq -r .access_token)
# 2. Oktaの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": "$OKTA_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 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, Claude"}]}' \
| jq -r '.content[] | select(.type == "text") | .text'SDKはAnthropicのアクセストークンが期限切れに近づくたびに、登録したidentity-tokenプロバイダーを呼び直します。したがってOktaのトークン取得処理は、同じトークンを使い回さず、呼ばれるたびに新しいトークンを返す実装にします。ant CLIはANTHROPIC_IDENTITY_TOKEN_FILEを交換のたびに再読み込みします。長時間起動しっぱなしのシェルでは、このファイルをタイマーで更新してください。
Anthropicトークンの寿命はOktaのJWTに引きずられる
交換で得るAnthropicトークンの寿命は、次の2つのうち短いほうです。1つはruleのtoken_lifetime_seconds(既定3,600秒、範囲は60〜86,400秒)、もう1つは提示したJWTの残り寿命の2倍です。下限は60秒です。
例のruleは600秒ですが、Oktaから受け取ったJWTの残りが120秒なら、Anthropicトークンは240秒で切れます。提示するJWTの残り寿命が短く、その2倍がruleの値を下回る場合、Anthropicトークンも短くなり、交換の頻度が上がります。
SDKは期限の120秒前に更新を試み、30秒前の更新が失敗するとエラーにします。トークンエンドポイントに届かない間は、残り約90秒有効なキャッシュ済みトークンを使い続けます。更新を試みるたびにOktaのトークンエンドポイントを呼ぶので、Oktaへのリクエスト数はこの更新サイクルに比例します。
JWTそのものにも上限があります。expとiatの差がissuerの上限(既定1時間、Consoleでissuerごとに変更可能)を超えるJWTは拒否されます。Oktaで発行するアクセストークンの寿命は、この上限以内に収めてください。
401が返ったときの切り分け
交換に成功するとsk-ant-oat01-で始まるaccess_tokenと、秒単位のexpires_inが返ります。失敗すると、どのチェックで落ちても同じ401のauthentication_error(メッセージはAuthentication failed)が返ります。ruleの設定を探る攻撃を防ぐための仕様で、原因はレスポンス本文からは分かりません。
拒否理由はClaude Consoleの認証履歴ページ(Settings → Workload identity内)に残ります。履歴の理由やJWTの中身から、次のように原因を絞ります。
| 拒否の型 | 原因 | 確認すること |
|---|---|---|
issの不一致(理由名は履歴で確認) | 原因JWTのissが登録したissuer_urlと一致しない | 確認することスキーム・ポート・末尾スラッシュまで1文字単位で比べる |
match_subject_prefix | 原因subがruleのsubject_prefixに合わない | 確認することsubはClient ID。大文字小文字を区別する |
| audienceの不一致 | 原因audにruleのaudienceが含まれない | 確認することaudが配列なら、いずれかの要素が完全一致する必要がある |
workspace_id_required | 原因ruleが複数ワークスペースで有効なのにworkspace_idが無い | 確認することANTHROPIC_WORKSPACE_IDを設定する |
jti_reused | 原因同じJWTを2回交換した | 確認することリトライや未更新のファイル再読込がないか。交換のたびに新規取得する |
exp切れ(理由名は履歴で確認) | 原因JWTが期限切れ(30秒の猶予を超過) | 確認することワークロードのホストの時計ずれも原因になる |
Okta側で鍵を切り替え、すぐ新しい鍵で署名を始めた場合は、AnthropicのJWKSキャッシュが更新されるまで最大1分ほど交換が失敗することがあります。署名鍵がJWKSに無い場合や、federation_rule_idのruleが存在しない・アーカイブ済みの場合も、同じ401になります。
Okta連携で最も多い原因は、issuer_urlの指定ミスです。パスに/oauth2/<auth-server-id>を含めず、Oktaの組織認可サーバー(/oauth2/v1/token)をそのまま指定すると、署名鍵が公開されていないため検証できません。
JWTの中身は、次のコマンドでデコードして確認できます。
jq -rR 'split(".")[1] | gsub("-";"+") | gsub("_";"/") | @base64d | fromjson' <<< "$OKTA_JWT"ルールのスコープを絞る
matchブロックは、subject_prefix・claims・conditionのうち最低1つを指定する必要があります。audienceだけのルールは登録時に拒否されます。複数の項目を指定した場合はAND条件で評価されます。ユースケースに合う最も狭い範囲に絞ります。
- Client IDを固定する:
subject_prefixにサービスアプリの完全なClient IDを、末尾の*なしで設定する - audienceを固定する: 認可サーバーに設定したaudience値と一致させ、別のaudience向けに発行されたトークンを拒否する
- カスタムクレームで絞る: より細かく制御したい場合は、認可サーバーのClaimsタブでクレームを追加し、ルールの
claimsマップやCEL条件式で照合する - サービスアプリごとに1ルール: 複数のサービスアプリで1つのルールを共有せず、サービスアプリごとに個別のfederation ruleを作る
claimsマップは各クレームの完全一致です。リストやネストしたクレームを扱うときはCEL条件式を使います。条件式はclaims変数にJWTの全クレームが入った状態で評価され、意図より多くの入力でtrueになると、そのぶん広くアクセスを許してしまいます。可能なら静的なマッチャーを優先します。
Oktaと他のIdPをどう使い分けるか
ワークロードの置き場所で選びます。
- Okta管理下のパイプラインやバッチ: Oktaがすでにワークロードの認証を担っているなら、サービスアプリを追加して本記事の流れで連携する
- Kubernetes: クラスタが投影するトークンをSDKが読むだけで済み、自前でトークンエンドポイントを呼ぶ実装が要らない。手順はWIFをKubernetesで使うにある
- AWS・Google Cloud: 実行環境がトークンを用意する。WIFをAWSで使うとWIFをGoogle Cloudで使うで設定を扱っている
- GitHub Actions: ワークフローが発行するトークンを使う。WIFをGitHub Actionsで使うが対応する
ant CLIの認証まわりはant CLIの認証設定にもあります。
APIキーからの移行で見る場所
SDKの認証情報の解決順では、ANTHROPIC_API_KEYとANTHROPIC_AUTH_TOKENがfederationの環境変数より上位にあります。キーが残っていると、federationを設定してもキーが使われ続けます。
移行中はキーを残したままfederationを並行稼働させ、ワークロードの内側でant auth statusを実行して、どの認証情報が選ばれているかを確認します。そのうえで、CIシークレット・コンテナの環境変数・シェルのプロファイルからキーを外し、最後にConsoleのSettings → API keysでキー自体を削除します。ANTHROPIC_API_KEY=""のように空文字でexportしても、空のキーが優先されてfederationに回らないので、unsetで消します。解決順の全体はWIFリファレンスにあります。
まとめ
Okta連携の肝は、ワークロードが自分でOktaのトークンエンドポイントを呼ぶ実装と、subject_prefixによるサービスアプリ単位のルール分離です。401は理由が本文に出ないので、まず履歴ページの拒否理由を見てからJWTをデコードして突き合わせます。