Create the app registration that represents the Claude API audience. changedmanage-claude/wif-providers/azure
Nearest release: v2.1.309, published 3 hours before this site recorded the change. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Recorded here
Lines+289added
Lines−289removed
From line
316
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits15to this page, all time
The whole hunk
from line 316, old and new numbered
/
from line 316
316316 }
317317 ```
318318
319 ```csharp C#
320 using Anthropic.Credentials;
321 // ...
322
323 var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
324 {
325 FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
326 OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
327 ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
328 WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
329 IdentityTokenProvider = new EntraTokenProvider(),
330 });
331 using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });
332
333 var message = await client.Messages.Create(new()
334 {
335 Model = Model.ClaudeOpus5_5,
336 MaxTokens = 1024,
337 Messages = [new UserMessageParam { Content = "Hello from Azure" }],
338 });
339 foreach (var block in message.Content)
340 {
341 if (block.Value is TextBlock textBlock)
342 {
343 Console.WriteLine(textBlock.Text);
344 }
345 }
346
347 class EntraTokenProvider : IIdentityTokenProvider
348 {
349 // The audience app registration's identifier URI (see Register the token audience).
350 private const string Audience = "api://<APP_ID>";
351
352 private static readonly HttpClient httpClient = new();
353
354 public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
355 {
356 // App Service, Functions, and Container Apps inject IDENTITY_ENDPOINT;
357 // VMs and VM Scale Sets use IMDS.
358 // With multiple user-assigned identities, append &client_id=<IDENTITY_CLIENT_ID>.
359 var identityEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
360 using var request = identityEndpoint is not null
361 ? new HttpRequestMessage(HttpMethod.Get,
362 $"{identityEndpoint}?api-version=2019-08-01&resource={Audience}")
363 {
364 Headers = { { "X-IDENTITY-HEADER", Environment.GetEnvironmentVariable("IDENTITY_HEADER") } },
365 }
366 : new HttpRequestMessage(HttpMethod.Get,
367 $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={Audience}")
368 {
369 Headers = { { "Metadata", "true" } },
370 };
371 using var response = await httpClient.SendAsync(request, ct);
372 response.EnsureSuccessStatusCode();
373 using var json = await JsonDocument.ParseAsync(
374 await response.Content.ReadAsStreamAsync(ct), default, ct);
375 return json.RootElement.GetProperty("access_token").GetString()!;
376 }
377 }
378 ```
379
319380 ```go Go
320381 package main
321382
from line 476
415476 // With multiple user-assigned identities, append &client_id=<IDENTITY_CLIENT_ID>.
416477 String identityEndpoint = System.getenv("IDENTITY_ENDPOINT");
417478 HttpRequest tokenRequest = identityEndpoint != null
418 ? HttpRequest.newBuilder(URI.create(identityEndpoint + "?api-version=2019-08-01&resource=" + audience))
419 .header("X-IDENTITY-HEADER", System.getenv("IDENTITY_HEADER"))
420 .build()
421 : HttpRequest.newBuilder(URI.create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=" + audience))
422 .header("Metadata", "true")
423 .build();
479 ? HttpRequest.newBuilder(URI.create(identityEndpoint + "?api-version=2019-08-01&resource=" + audience))
480 .header("X-IDENTITY-HEADER", System.getenv("IDENTITY_HEADER"))
481 .build()
482 : HttpRequest.newBuilder(URI.create("http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=" + audience))
483 .header("Metadata", "true")
484 .build();
424485
425486 // The Java SDK takes the token itself or a file path, not a function; with
426487 // source("value"), path(...) holds the token. The SDK sends that same token
from line 493
432493 String entraToken = new ObjectMapper().readTree(response.body()).get("access_token").asText();
433494
434495 AnthropicClient client = AnthropicOkHttpClient.builder()
435 .configurationProvider(InMemoryProfileConfigProvider.of(ProfileConfig.builder()
436 .organizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
437 .workspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
438 .authentication(AuthenticationConfig.builder()
439 .type(AuthenticationType.OIDC_FEDERATION)
440 .federationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
441 .serviceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
442 .identityToken(IdentityTokenConfig.builder()
443 .source("value")
444 .path(entraToken)
445 .build())
446 .build())
447 .build()))
448 .build();
496 .configurationProvider(InMemoryProfileConfigProvider.of(ProfileConfig.builder()
497 .organizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
498 .workspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
499 .authentication(AuthenticationConfig.builder()
500 .type(AuthenticationType.OIDC_FEDERATION)
501 .federationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
502 .serviceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
503 .identityToken(IdentityTokenConfig.builder()
504 .source("value")
505 .path(entraToken)
506 .build())
507 .build())
508 .build()))
509 .build();
449510
450511 var message = client.messages().create(MessageCreateParams.builder()
451 .model(Model.CLAUDE_OPUS_5_5)
452 .maxTokens(1024)
453 .addUserMessage("Hello from Azure")
454 .build());
512 .model(Model.CLAUDE_OPUS_5_5)
513 .maxTokens(1024)
514 .addUserMessage("Hello from Azure")
515 .build());
455516
456517 IO.println(message.content());
457518 }
458519 ```
459520
460 ```csharp C#
461 using Anthropic.Credentials;
462 // ...
463
464 var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
465 {
466 FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
467 OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
468 ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
469 WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
470 IdentityTokenProvider = new EntraTokenProvider(),
471 });
472 using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });
473
474 var message = await client.Messages.Create(new()
475 {
476 Model = Model.ClaudeOpus5_5,
477 MaxTokens = 1024,
478 Messages = [new UserMessageParam { Content = "Hello from Azure" }],
479 });
480 foreach (var block in message.Content)
481 {
482 if (block.Value is TextBlock textBlock)
483 {
484 Console.WriteLine(textBlock.Text);
485 }
486 }
487
488 class EntraTokenProvider : IIdentityTokenProvider
489 {
490 // The audience app registration's identifier URI (see Register the token audience).
491 private const string Audience = "api://<APP_ID>";
492
493 private static readonly HttpClient httpClient = new();
494
495 public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
496 {
497 // App Service, Functions, and Container Apps inject IDENTITY_ENDPOINT;
498 // VMs and VM Scale Sets use IMDS.
499 // With multiple user-assigned identities, append &client_id=<IDENTITY_CLIENT_ID>.
500 var identityEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
501 using var request = identityEndpoint is not null
502 ? new HttpRequestMessage(HttpMethod.Get,
503 $"{identityEndpoint}?api-version=2019-08-01&resource={Audience}")
504 {
505 Headers = { { "X-IDENTITY-HEADER", Environment.GetEnvironmentVariable("IDENTITY_HEADER") } },
506 }
507 : new HttpRequestMessage(HttpMethod.Get,
508 $"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource={Audience}")
509 {
510 Headers = { { "Metadata", "true" } },
511 };
512 using var response = await httpClient.SendAsync(request, ct);
513 response.EnsureSuccessStatusCode();
514 using var json = await JsonDocument.ParseAsync(
515 await response.Content.ReadAsStreamAsync(ct), default, ct);
516 return json.RootElement.GetProperty("access_token").GetString()!;
517 }
518 }
519 ```
520
521521 ```php PHP
522522 use Anthropic\Client;
523523 use Anthropic\Lib\Credentials\CredentialResult;
from line 614
614614
615615### Verify the setup
616616
617From your Azure resource, run the cURL exchange shown in [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#acquire-and-use-the-token) 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. If the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`), check the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console for the deny reason, then decode the Entra token (see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) for the command) and check the most common Azure-side causes:
617From your Azure resource, run the cURL exchange shown in [Acquire and use the token](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#acquire-and-use-the-token) and confirm that `POST /v1/oauth/token` returns a `200` with an `access_token` starting with `sk-ant-oat01-` and an `expires_in` value in seconds. If the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`), check the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console for the deny reason, then decode the Entra token (see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) for the command) and check the most common Azure-side causes:
618618
619619* **Issuer mismatch:** The registered `issuer_url` must match the token's `iss` claim exactly. A v2.0 token carries `https://login.microsoftonline.com/<TENANT_ID>/v2.0`; if the decoded `ver` claim is `1.0`, see [If your tokens are v1.0](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0).
620620* **Token lifetime:** Managed identity tokens carry up to 24 hours between `iat` and `exp`. If the issuer still has the wizard's `7500` (or the 1-hour default), raise `max_jwt_lifetime_seconds` to `86400` as described in [Configure Anthropic](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic).
from line 951
951951 }
952952 ```
953953
954 ```csharp C#
955 using Anthropic.Credentials;
956 // ...
957
958 var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
959 {
960 FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
961 OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
962 ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
963 WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
964 IdentityTokenProvider = new EntraFederationTokenProvider(),
965 });
966 using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });
967
968 var message = await client.Messages.Create(new()
969 {
970 Model = Model.ClaudeOpus5_5,
971 MaxTokens = 1024,
972 Messages = [new UserMessageParam { Content = "Hello from Azure" }],
973 });
974 foreach (var block in message.Content)
975 {
976 if (block.Value is TextBlock textBlock)
977 {
978 Console.WriteLine(textBlock.Text);
979 }
980 }
981
982 class EntraFederationTokenProvider : IIdentityTokenProvider
983 {
984 private static readonly HttpClient Http = new();
985
986 public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
987 {
988 var federatedToken = await File.ReadAllTextAsync(
989 Environment.GetEnvironmentVariable("AZURE_FEDERATED_TOKEN_FILE")!, ct);
990 var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
991 var form = new FormUrlEncodedContent(new Dictionary<string, string>
992 {
993 ["client_id"] = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")!,
994 ["grant_type"] = "client_credentials",
995 ["scope"] = "api://<APP_ID>/.default",
996 ["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
997 ["client_assertion"] = federatedToken,
998 });
999 var response = await Http.PostAsync(
1000 $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", form, ct);
1001 response.EnsureSuccessStatusCode();
1002 using var json = await JsonDocument.ParseAsync(
1003 await response.Content.ReadAsStreamAsync(ct), default, ct);
1004 return json.RootElement.GetProperty("access_token").GetString()!;
1005 }
1006 }
1007 ```
1008
9541009 ```go Go
9551010 package main
9561011
from line 1103
10481103
10491104 void main() throws Exception {
10501105 var form = Map.of(
1051 "client_id", System.getenv("AZURE_CLIENT_ID"),
1052 "grant_type", "client_credentials",
1053 "scope", "api://<APP_ID>/.default",
1054 "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
1055 "client_assertion", Files.readString(Path.of(System.getenv("AZURE_FEDERATED_TOKEN_FILE"))))
1056 .entrySet().stream()
1057 .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
1058 .collect(Collectors.joining("&"));
1106 "client_id", System.getenv("AZURE_CLIENT_ID"),
1107 "grant_type", "client_credentials",
1108 "scope", "api://<APP_ID>/.default",
1109 "client_assertion_type", "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
1110 "client_assertion", Files.readString(Path.of(System.getenv("AZURE_FEDERATED_TOKEN_FILE"))))
1111 .entrySet().stream()
1112 .map(entry -> entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), UTF_8))
1113 .collect(Collectors.joining("&"));
10591114 var request = HttpRequest.newBuilder(URI.create(
1060 "https://login.microsoftonline.com/" + System.getenv("AZURE_TENANT_ID") + "/oauth2/v2.0/token"))
1061 .header("content-type", "application/x-www-form-urlencoded")
1062 .POST(HttpRequest.BodyPublishers.ofString(form))
1063 .build();
1115 "https://login.microsoftonline.com/" + System.getenv("AZURE_TENANT_ID") + "/oauth2/v2.0/token"))
1116 .header("content-type", "application/x-www-form-urlencoded")
1117 .POST(HttpRequest.BodyPublishers.ofString(form))
1118 .build();
10641119
10651120 // The Java SDK takes the token itself or a file path, not a function; with
10661121 // source("value"), path(...) holds the token. The SDK sends that same token
from line 1127
10721127 String entraToken = new ObjectMapper().readTree(response.body()).get("access_token").asText();
10731128
10741129 AnthropicClient client = AnthropicOkHttpClient.builder()
1075 .configurationProvider(InMemoryProfileConfigProvider.of(ProfileConfig.builder()
1076 .organizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
1077 .workspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
1078 .authentication(AuthenticationConfig.builder()
1079 .type(AuthenticationType.OIDC_FEDERATION)
1080 .federationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
1081 .serviceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
1082 .identityToken(IdentityTokenConfig.builder()
1083 .source("value")
1084 .path(entraToken)
1085 .build())
1086 .build())
1087 .build()))
1088 .build();
1130 .configurationProvider(InMemoryProfileConfigProvider.of(ProfileConfig.builder()
1131 .organizationId(System.getenv("ANTHROPIC_ORGANIZATION_ID"))
1132 .workspaceId(System.getenv("ANTHROPIC_WORKSPACE_ID"))
1133 .authentication(AuthenticationConfig.builder()
1134 .type(AuthenticationType.OIDC_FEDERATION)
1135 .federationRuleId(System.getenv("ANTHROPIC_FEDERATION_RULE_ID"))
1136 .serviceAccountId(System.getenv("ANTHROPIC_SERVICE_ACCOUNT_ID"))
1137 .identityToken(IdentityTokenConfig.builder()
1138 .source("value")
1139 .path(entraToken)
1140 .build())
1141 .build())
1142 .build()))
1143 .build();
10891144
10901145 var message = client.messages().create(MessageCreateParams.builder()
1091 .model(Model.CLAUDE_OPUS_5_5)
1092 .maxTokens(1024)
1093 .addUserMessage("Hello from Azure")
1094 .build());
1146 .model(Model.CLAUDE_OPUS_5_5)
1147 .maxTokens(1024)
1148 .addUserMessage("Hello from Azure")
1149 .build());
10951150
10961151 IO.println(message.content());
10971152 }
10981153 ```
10991154
1100 ```csharp C#
1101 using Anthropic.Credentials;
1102 // ...
1103
1104 var credentials = new WorkloadIdentityCredentials(new WorkloadIdentityOptions
1105 {
1106 FederationRuleId = Environment.GetEnvironmentVariable("ANTHROPIC_FEDERATION_RULE_ID")!,
1107 OrganizationId = Environment.GetEnvironmentVariable("ANTHROPIC_ORGANIZATION_ID"),
1108 ServiceAccountId = Environment.GetEnvironmentVariable("ANTHROPIC_SERVICE_ACCOUNT_ID"),
1109 WorkspaceId = Environment.GetEnvironmentVariable("ANTHROPIC_WORKSPACE_ID"),
1110 IdentityTokenProvider = new EntraFederationTokenProvider(),
1111 });
1112 using var client = new AnthropicClient(new ClientOptions { Credentials = credentials });
1113
1114 var message = await client.Messages.Create(new()
1115 {
1116 Model = Model.ClaudeOpus5_5,
1117 MaxTokens = 1024,
1118 Messages = [new UserMessageParam { Content = "Hello from Azure" }],
1119 });
1120 foreach (var block in message.Content)
1121 {
1122 if (block.Value is TextBlock textBlock)
1123 {
1124 Console.WriteLine(textBlock.Text);
1125 }
1126 }
1127
1128 class EntraFederationTokenProvider : IIdentityTokenProvider
1129 {
1130 private static readonly HttpClient Http = new();
1131
1132 public async Task<string> GetIdentityTokenAsync(CancellationToken ct = default)
1133 {
1134 var federatedToken = await File.ReadAllTextAsync(
1135 Environment.GetEnvironmentVariable("AZURE_FEDERATED_TOKEN_FILE")!, ct);
1136 var tenantId = Environment.GetEnvironmentVariable("AZURE_TENANT_ID");
1137 var form = new FormUrlEncodedContent(new Dictionary<string, string>
1138 {
1139 ["client_id"] = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")!,
1140 ["grant_type"] = "client_credentials",
1141 ["scope"] = "api://<APP_ID>/.default",
1142 ["client_assertion_type"] = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
1143 ["client_assertion"] = federatedToken,
1144 });
1145 var response = await Http.PostAsync(
1146 $"https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token", form, ct);
1147 response.EnsureSuccessStatusCode();
1148 using var json = await JsonDocument.ParseAsync(
1149 await response.Content.ReadAsStreamAsync(ct), default, ct);
1150 return json.RootElement.GetProperty("access_token").GetString()!;
1151 }
1152 }
1153 ```
1154
11551155 ```php PHP
11561156 use Anthropic\Client;
11571157 use Anthropic\Lib\Credentials\CredentialResult;
from line 1241
12411241
12421242### Verify the setup
12431243
1244From inside a labeled pod, run the cURL exchange shown in [Acquire and use the token on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#acquire-and-use-the-token-2) 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. If the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`), check the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console for the deny reason, then decode the Entra-issued token from step 1 (see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) for the command) and check the most common Azure-side causes:
1244From inside a labeled pod, run the cURL exchange shown in [Acquire and use the token on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#acquire-and-use-the-token-2) and confirm that `POST /v1/oauth/token` returns a `200` with an `access_token` starting with `sk-ant-oat01-` and an `expires_in` value in seconds. If the exchange fails with the opaque `401` `authentication_error` response (message `Authentication failed`), check the [authentication history page](https://platform.claude.com/settings/workload-identity-federation?tab=history) in the Claude Console for the deny reason, then decode the Entra-issued token from step 1 (see [Troubleshoot a failed exchange](https://platform.claude.com/docs/en/manage-claude/wif-reference#troubleshoot-a-failed-exchange) for the command) and check the most common Azure-side causes:
12451245
12461246* **Issuer mismatch:** The registered `issuer_url` must match the token's `iss` claim exactly. A v2.0 token carries `https://login.microsoftonline.com/<TENANT_ID>/v2.0`; if the decoded `ver` claim is `1.0`, see [If your tokens are v1.0](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#if-your-tokens-are-v1-0).
12471247* **Token lifetime:** If a tenant token-lifetime policy or CAE extends the `client_credentials` token past 7,500 seconds, raise the issuer's `max_jwt_lifetime_seconds` as described in [Configure Anthropic for Entra Workload Identity on AKS](https://platform.claude.com/docs/en/manage-claude/wif-providers/azure#configure-anthropic-2).
No line in this hunk matches that.