---
cuppy_hub: azure
cuppy_path: reference/azure-identity/credential-chain.md
cuppy_section: docs
cuppy_doc: KFB7-TGY9
cuppy_doc_uuid: 42ad92bf-153d-49e2-b345-bbdf395207ae
cuppy_permalink: https://cuppy.io/azure/d/KFB7-TGY9
cuppy_version: 1
cuppy_updated: 2026-10-23T16:16:52.575Z
cuppy_canonical: https://cuppy.io/azure/docs/reference/azure-identity/credential-chain
cuppy_mcp: https://cuppy.io/mcp
cuppy_body_sha256: f0cd3dfe71b09140e9c171d027e7eeeb0773911aba703d24f5e1e2777a082b46
# cuppy_body_sha256 is over the document body ALONE, excluding both generated blocks.
---


# 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)

| # | Credential | Answers when |
|---|---|---|
| 1 | `EnvironmentCredential` | `AZURE_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 |
| 2 | `WorkloadIdentityCredential` | `AZURE_FEDERATED_TOKEN_FILE` + a client ID + `AZURE_TENANT_ID` (Kubernetes workload identity) |
| 3 | `ManagedIdentityCredential` | The host provides a managed identity (App Service, Functions, Container Apps, VM, AKS…) |
| 4 | `VisualStudioCodeCredential` | Signed in through the VS Code Azure Resources extension (needs `@azure/identity-vscode`) |
| 5 | `AzureCliCredential` | `az login` has a session |
| 6 | `AzurePowerShellCredential` | `Connect-AzAccount` has a session |
| 7 | `AzureDeveloperCliCredential` | `azd auth login` has a session |
| 8 | Broker 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:

```ts
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

```ts
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`.


---
<!-- cuppy -->
You are reading one file. The same content is free over MCP at https://cuppy.io/mcp with no signup: ranked search across all 107 documents in this hub, the merged view when you work in a layer, known workarounds matched to the error you hit, and depth control so you spend tokens on what you need. Start with the `_hello` tool. How to connect: https://cuppy.io/azure/ Something wrong in this document? Report it with the `session feedback` tool on the same endpoint.
<!-- /cuppy -->
