Claude Media
ClaudeのWIFをOktaと連携する手順

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

    ワークロードがOktaに認証する

    カスタム認可サーバーのトークンエンドポイントにclient_credentialsで認証し、JWTを受け取ります。

  2. 2

    JWTをAnthropicのトークンに交換する

    /v1/oauth/tokenにJWTとfederation ruleのIDなどを渡すと、sk-ant-oat01-で始まる短命アクセストークンが返ります。

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

    Service app integrationを作る

    Okta Admin Consoleで新規アプリ統合をAPI Services(OIDC、machine-to-machine)タイプで作成し、生成されたClient IDを控えます。

  2. 2

    クライアント認証方式を選ぶ

    鍵レスにするならPublic key / Private key(private_key_jwt)を選び、ワークロードの公開JWKを登録します。シークレットを安全に保管できる環境ならクライアントシークレットでも構いません。公式の例を試すときはアプリ側のDPoP要件の無効化が必要になる場合があり、本番構成は組織のセキュリティ要件に従います。

  3. 3

    audienceを設定する

    カスタム認可サーバーでaudienceを https://api.anthropic.com にします。発行されるアクセストークンのaudクレームがこの値になり、Anthropicはこの固定値と照合します。

  4. 4

    scopeを許可する

    認可サーバーにサービスアプリがリクエストできるscope(例: anthropic.access)を最低1つ用意します。許可されたscopeを含まないclient_credentialsリクエストはOktaに拒否されます。

  5. 5

    アクセスポリシーを作る

    上の「scopeを許可する」で用意したscopeを、サービスアプリがリクエストできるルールを持つアクセスポリシーを作成します。

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

anthropic.access

Oktaのscope

Okta認可サーバーに作り、サービスアプリに許可します。Oktaがトークンを発行する条件で、Claude APIで何が呼べるかは決めません。

workspace:developer

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をデコードして突き合わせます。

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