Oktaは、OAuth 2.0のclient_credentialsグラントを通じてサービスアプリケーションにOIDCアクセストークンを発行することで、ワークロードアイデンティティプロバイダーとして機能できます。ワークロードはOktaに対して認証を行い(通常はprivate_key_jwtを使用するため、共有シークレットは保存されません)、署名された「JSON Web Token」(JWT)を受け取り、そのJWTをAnthropicと交換して短期間有効なアクセストークンを取得します。
Okta認可サーバーの発行者URLはhttps://<your-domain>.okta.com/oauth2/<auth-server-id>という形式です。組み込みのデフォルトサーバーを使用する場合、パスは/oauth2/defaultになります。
Oktaのカスタム認可サーバー(defaultのものを含む)を使用する必要があります。Okta組織認可サーバー(パスに認可サーバーIDを含まない/oauth2/v1/tokenエンドポイント)によって直接発行されたトークンは、Oktaがそれらの署名鍵を公開していないため、外部の当事者が検証することはできません。
Oktaの設定と認証には、このドキュメントの範囲外となる多くの方法があります。設定と認証のメカニズムが、お客様の会社のガイダンスとセキュリティプラクティスに従っていることを確認してください。
/v1/tokenエンドポイントからトークンをリクエストでき、api.anthropic.comに到達できるワークロード。大まかには、次のことを行う必要があります。
正確なナビゲーションは、Okta組織の設定と管理コンソールのバージョンによって異なります。以下の番号付きの手順は、一般的な方法の1つを説明しています。
private_key_jwt)を選択し、ワークロードの公開JWKを登録します。あるいは、環境が安全に保存できる場合はクライアントシークレットを使用します。以下の例では、アプリケーションでDPoP要件を無効にする必要がある場合があります。本番環境の構成が組織のセキュリティ要件に準拠していることを確認してください。audクレームを持つように、オーディエンスをhttps://api.anthropic.comに設定します。Anthropicはこの固定値に対してaudを検証します。anthropic.access)。Oktaは、付与されたスコープを含まないclient_credentialsリクエストを拒否します。client_credentialsを使用するサービスアプリの場合、Oktaは発行されたアクセストークンのsubクレームをアプリケーションのClient IDに、issを認可サーバーの発行者URLに設定します。
Claude Consoleで、Settings → Workload identityを開き、Connect workloadをクリックして、Custom OIDCを選択します。ウィザードが、発行者の登録、サービスアカウントの作成、フェデレーションルールの作成を順を追って案内します。
ウィザードがこれらのリソースを作成します。ウィザードに入力する場合でも、Admin API に送信する場合でも、以下の値を使用してください。
フェデレーション発行者: Oktaカスタム認可サーバーのURLとディスカバリーモードを使用します。AnthropicはOktaの.well-known/openid-configurationディスカバリードキュメントを読み取り、そこで公開されているjwks_uriからJWKSを取得します。
{
"name": "okta-prod",
"issuer_url": "https://acme.okta.com/oauth2/aus1a2b3c4d5e6f7g8h9",
"jwks": { "type": "discovery" }
}フェデレーションルール: Oktaのsubクレーム(サービスアプリのClient ID)でマッチングします。Oktaでカスタムクレームを定義した場合は、代わりにclaimsマップまたはCELのconditionを使用してそれらでマッチングできます。
{
"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
}ワークロードのランタイム内でトークンを利用可能にする(投影されたファイルまたはローカルメタデータエンドポイントを通じて)プラットフォームネイティブのプロバイダー(AWS、Google Cloud、Kubernetes)とは異なり、Oktaはそれを行いません。ワークロードはOktaのトークンエンドポイントを呼び出してJWTを取得し、そのJWTをアイデンティティトークンとしてAnthropic SDKに渡す必要があります。
各SDKタブは呼び出し可能なパターンを示しています。Anthropic SDKは、Anthropicアクセストークンの有効期限が近づくたびにアイデンティティトークンプロバイダーを再度呼び出すため、Oktaフェッチャーはトークンを無期限にキャッシュするのではなく、呼び出しごとに新しいトークンを返す必要があります。ant CLIは交換ごとにANTHROPIC_IDENTITY_TOKEN_FILEを再読み込みするため、長時間実行されるシェルではタイマーでそのファイルを更新してください。
交換が成功すると、sk-ant-oat01-で始まるaccess_tokenと、秒単位のexpires_in値が返されます。400 invalid_grantが発生した場合は、失敗した交換のトラブルシューティングを参照してください。Okta側で最も一般的な原因はissuer_urlの不一致です(/oauth2/<auth-server-id>パスを含める必要があります。Okta組織認可サーバーは使用できません)。
同じOkta認可サーバー配下の複数のサービスアプリは、同じ発行者を共有します。
subject_prefixを省略したルールは、そのサーバー上のすべてのサービスアプリに
マッチするため、サービスアプリを登録できるチームであれば誰でもフェデレーション
されたAnthropicトークンを取得できてしまいます。
ルールのmatchブロックを、ユースケースに適合する最も狭いスコープにロックしてください。
subject_prefixを、末尾に*を付けずにサービスアプリの完全なClient IDに設定します。audience値にマッチさせることで、異なるオーディエンス向けに発行されたトークンが拒否されるようにします。claimsマップまたはCELのconditionでマッチングします。Was this page helpful?
import os
import httpx
import anthropic
from anthropic import WorkloadIdentityCredentials
def fetch_okta_token() -> str:
response = httpx.post(
f"{os.environ['OKTA_ISSUER']}/v1/token",
data={
"grant_type": "client_credentials",
"scope": "anthropic.access",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
# Oktaアプリの秘密鍵で署名したRFC 7523のclient_assertion JWTを構築します
"client_assertion": build_signed_client_assertion(),
},
)
response.raise_for_status()
return response.json()["access_token"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_okta_token,
federation_rule_id=os.environ["ANTHROPIC_FEDERATION_RULE_ID"],
organization_id=os.environ["ANTHROPIC_ORGANIZATION_ID"],
service_account_id=os.environ["ANTHROPIC_SERVICE_ACCOUNT_ID"],
workspace_id=os.environ.get("ANTHROPIC_WORKSPACE_ID"),
),
)
message = client.messages.create(
model="claude-opus-5",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello, Claude"}],
)
print(next(block.text for block in message.content if block.type == "text"))