DefaultAzureCredential and the credential chain (TypeScript)

How @azure/identity picks an identity — the exact DefaultAzureCredential order (Environment, Workload Identity, Managed Identity, VS Code, Azure CLI, PowerShell, azd, broker), the environment variables each step reads, AZURE_TOKEN_CREDENTIALS to pin one, the AZURE_CLIENT_ID trap for managed identities, which credential to use in production, ChainedTokenCredential, and logging which identity actually signed in.

markdownreferencev1KFB7-TGY9stage: buildtrust: officialservice: azure-identityinterface: sdkDefaultAzureCredentialcredential chain orderChainedTokenCredentialManagedIdentityCredential clientIdEnvironmentCredentialWorkloadIdentityCredentialAzureCliCredentialAZURE_TOKEN_CREDENTIALSAZURE_CLIENT_IDAZURE_TENANT_IDAZURE_CLIENT_SECRETAZURE_CLIENT_CERTIFICATE_PATHAZURE_FEDERATED_TOKEN_FILEmanagedIdentityClientIdrequiredEnvVarsallowLoggingAccountIdentifiersAZURE_LOG_LEVELwrong identity@azure/identity 4.14

Created 21 Oct 2026 · Modified 23 Oct 2026 · 1 version

DefaultAzureCredential and the credential chain

DefaultAzureCredential tries credentials in a fixed order and uses the first one that returns a token, so "it works on my machine" usually means your Azure CLI login answered, and "403 in Azure" usually means a different identity answered than the one you assigned the role to. Read the order, then log which account signed in.

The order (@azure/identity 4.14.0)

#CredentialAnswers when
1EnvironmentCredentialAZURE_TENANT_ID + AZURE_CLIENT_ID + AZURE_CLIENT_SECRET, or AZURE_CLIENT_CERTIFICATE_PATH (PEM with private key; pfx isn't supported). Secret wins if both are set
2WorkloadIdentityCredentialAZURE_FEDERATED_TOKEN_FILE + a client ID + AZURE_TENANT_ID (Kubernetes workload identity)
3ManagedIdentityCredentialThe host provides a managed identity (App Service, Functions, Container Apps, VM, AKS…)
4VisualStudioCodeCredentialSigned in through the VS Code Azure Resources extension (needs @azure/identity-vscode)
5AzureCliCredentialaz login has a session
6AzurePowerShellCredentialConnect-AzAccount has a session
7AzureDeveloperCliCredentialazd auth login has a session
8Broker credential@azure/identity-broker is installed (Windows account broker)

Continuation: since 3.3.0, the developer credentials (4–7) are all tried even if one errors. A deployed credential (2–3) that attempts a token and fails stops the chain with an exception, so a broken managed identity doesn't silently fall through to something else.

The AZURE_CLIENT_ID trap

For steps 2 and 3 the client ID is managedIdentityClientId (option) or the AZURE_CLIENT_ID environment variable. Set AZURE_CLIENT_ID for a service principal but forget the secret, and step 1 is skipped while step 3 asks the host for a user-assigned managed identity with that client ID. On a VM, that is the IMDS answer The requested identity hasn't been assigned to this resource. (the hub's reading of the two code paths). Set the variable only to the identity you mean.

Pin the credential in production

AZURE_TOKEN_CREDENTIALS narrows the chain: prod (steps 1–3), dev (steps 4–8), or one name: EnvironmentCredential, WorkloadIdentityCredential, ManagedIdentityCredential, VisualStudioCodeCredential, AzureCliCredential, AzurePowerShellCredential, AzureDeveloperCliCredential. An unknown value throws. Add requiredEnvVars so a missing variable fails at startup instead of falling back to the whole chain. In code where only one identity is right, use that credential class directly:

import {
  ChainedTokenCredential,
  DefaultAzureCredential,
  ManagedIdentityCredential,
  AzureCliCredential,
} from "@azure/identity";

// Production on Azure: exactly this user-assigned identity, nothing else.
export const prodCredential = new ManagedIdentityCredential({
  clientId: process.env.APP_IDENTITY_CLIENT_ID ?? "",
});

// One code path for laptop and Azure, with an explicit order.
export const appCredential = new ChainedTokenCredential(prodCredential, new AzureCliCredential());

// Or keep DefaultAzureCredential but fail fast if it isn't pinned.
export const pinned = new DefaultAzureCredential({
  requiredEnvVars: ["AZURE_TOKEN_CREDENTIALS"],
  managedIdentityClientId: process.env.APP_IDENTITY_CLIENT_ID,
  loggingOptions: { allowLoggingAccountIdentifiers: true },
});

The JavaScript library has no exclude…Credential options (other languages do): build a ChainedTokenCredential instead.

See which identity answered

import { setLogLevel } from "@azure/logger";
setLogLevel("info"); // or AZURE_LOG_LEVEL=info

With loggingOptions.allowLoggingAccountIdentifiers: true, a successful sign-in logs [Authenticated account] Client ID: … Tenant ID: … User Principal Name: … Object ID (user): …. Compare that object ID with the role assignment's principal. Logs carry sensitive data: don't ship them at info in production.

Tokens and scopes

A credential returns a token for a scope, <resource>/.default. The SDK clients pass the right one for you; you need it only for raw REST calls (credential.getToken("https://management.azure.com/.default")). Resource Manager is https://management.azure.com/.default; Key Vault is https://vault.azure.net/.default. Each service family in this hub gives its own scope.

Managed identity tokens are valid for 24 hours and cached by the platform, so a role or group change can take hours to reach a managed identity: see reference/managed-identity/errors.md.

Next

Error messages from each credential: reference/azure-identity/errors.md. Setting up a managed identity: guides/managed-identity/use-managed-identity.md.

Read this document as markdown — the same bytes an agent receives.