Was this page helpful?
Azure workloads authenticate to the Claude API by presenting a JSON Web Token (JWT) issued by Microsoft Entra ID, then exchanging it for a short-lived Anthropic access token. There are two common ways to obtain the Entra-issued token:
http://169.254.169.254/metadata/identity/oauth2/token and receives a JWT for its assigned identity.AZURE_FEDERATED_TOKEN_FILE. The workload exchanges that token at Entra for an Entra-issued access token.In both cases the Entra-issued token you present to Anthropic has the tenant-specific issuer https://login.microsoftonline.com/<TENANT_ID>/v2.0 and carries the managed identity's object ID in the sub and oid claims. You register that issuer with Anthropic once, write a federation rule that matches the expected claims, and your workload exchanges its Entra token for an sk-ant-oat01-... access token at runtime.
AKS pods can alternatively skip the Entra exchange and present the Kubernetes-projected service account token to Anthropic directly. That path registers your AKS cluster's OIDC issuer with Anthropic instead of your Entra tenant. See Kubernetes for that flow.
Set up the identity that Azure will issue tokens for. Choose the path that matches where your workload runs.
An Entra-issued token for a managed identity carries these claims:
{
"iss": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
"sub": "9f8e7d6c-1a2b-3c4d-5e6f-...",
"aud": "https://api.anthropic.com",
"oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
"tid": "<TENANT_ID>",
"azp": "<CLIENT_ID>",
"exp": 1775527120
}sub and oid are identical (the managed identity's object ID). azp is the application or client ID. Match on oid to authorize one specific identity, or on azp to authorize any identity associated with an application registration. The tid claim repeats your tenant ID; matching on it is defense in depth, because the issuer URL already pins the tenant.
Follow the setup walkthrough to register a federation issuer, create an Anthropic service account, and create a federation rule in the Claude Console. In the Console, choose the OIDC provider option and supply the Entra-specific values that follow.
Federation issuer: Entra publishes an OIDC discovery document at the per-tenant issuer URL, so use discovery mode. Each Azure tenant you federate needs its own issuer record.
{
"name": "azure-prod-tenant",
"issuer_url": "https://login.microsoftonline.com/<TENANT_ID>/v2.0",
"jwks_source": "discovery"
}Depending on the token version, the iss claim may be https://sts.windows.net/<TENANT_ID>/ instead. Decode your managed-identity token (the Verify section below shows how) and register whichever iss value it contains. The two URLs share the same JWKS, so discovery mode works for either.
Federation rule: Match on the managed identity's object ID and your tenant ID.
{
"name": "azure-inference-worker",
"issuer_id": "fdis_...",
"match": {
"audience": "https://api.anthropic.com",
"claims": {
"oid": "9f8e7d6c-1a2b-3c4d-5e6f-...",
"tid": "<TENANT_ID>"
}
},
"target": {
"type": "service_account",
"service_account_id": "svac_..."
},
"workspace_id": "wrkspc_...",
"oauth_scope": "workspace:developer",
"token_lifetime_seconds": 600
}At runtime your workload fetches its Entra token, exchanges it at POST /v1/oauth/token, and uses the returned bearer token to call Claude. Each Anthropic SDK handles the exchange and refresh loop when you supply a token-provider callable, as shown in the following examples. The cURL tab shows the raw flow.
On AKS, the file at AZURE_FEDERATED_TOKEN_FILE is a Kubernetes-projected service account token signed by your cluster's OIDC issuer, not an Entra-issued token. To stay on the Entra-mediated path described on this page, exchange that token at https://login.microsoftonline.com/<TENANT_ID>/oauth2/v2.0/token (federated client_credentials grant) first, then pass the resulting Entra access token to the Anthropic SDK as the identity token.
Alternatively, register your AKS cluster's OIDC issuer with Anthropic directly and skip the Entra hop. See Kubernetes for that pattern.
From your Azure resource, run the cURL exchange shown earlier and confirm that POST /v1/oauth/token returns a 200 with an access_token beginning with sk-ant-oat01- and an expires_in value in seconds. On 400 invalid_grant, see Troubleshoot a failed exchange; the most common Azure-side cause is a mismatch between the issuer_url you registered and the iss claim in your decoded token. They must match exactly. For managed-identity tokens the iss value is either https://login.microsoftonline.com/<TENANT_ID>/v2.0 or https://sts.windows.net/<TENANT_ID>/.
The oid claim is a managed identity's GUID and has no stable prefix. A
subject_prefix with * matches arbitrary identities in the tenant, so any
workload that holds a managed identity could obtain a federated Anthropic
token.
Lock the rule's match block to the narrowest scope that fits your use case:
oid as an exact value: Set claims.oid to the managed identity's full object ID and never use subject_prefix for Azure tokens.tid as defense in depth: The issuer URL already pins your tenant, but adding claims.tid guards against configuration drift if the issuer record is later edited.audience to https://api.anthropic.com so tokens minted for other resources are rejected.import os
import anthropic
import requests
from anthropic import WorkloadIdentityCredentials
IMDS_URL = "http://169.254.169.254/metadata/identity/oauth2/token"
def fetch_entra_token() -> str:
"""Fetch a managed identity token from Azure IMDS."""
response = requests.get(
IMDS_URL,
headers={"Metadata": "true"},
params={"api-version": "2018-02-01", "resource": "https://api.anthropic.com"},
timeout=5,
)
response.raise_for_status()
return response.json()["access_token"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_entra_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"],
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from Azure"}],
)
print(message.content[0].text)import os
from pathlib import Path
import httpx
import anthropic
from anthropic import WorkloadIdentityCredentials
def fetch_entra_token_via_federation() -> str:
federated_token = Path(os.environ["AZURE_FEDERATED_TOKEN_FILE"]).read_text()
response = httpx.post(
f"https://login.microsoftonline.com/{os.environ['AZURE_TENANT_ID']}/oauth2/v2.0/token",
data={
"client_id": os.environ["AZURE_CLIENT_ID"],
"grant_type": "client_credentials",
"scope": "https://api.anthropic.com/.default",
"client_assertion_type": "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
"client_assertion": federated_token,
},
)
response.raise_for_status()
return response.json()["access_token"]
client = anthropic.Anthropic(
credentials=WorkloadIdentityCredentials(
identity_token_provider=fetch_entra_token_via_federation,
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"],
),
)
message = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
messages=[{"role": "user", "content": "Hello from Azure"}],
)
print(message.content[0].text)