Photo by Matt Halls on Unsplash

Photo by Matt Halls on Unsplash
Introduction
I have been using the DefaultAzureCredential class for a long time without understanding how it works. So, I jotted down my notes and learnings in this write-up for future me — and maybe you will find it useful too.
TokenCredential
TokenCredential is the abstract base class representing a source of authentication tokens for Azure services. Many classes derive from TokenCredential but the most interesting ones are DefaultAzureCredential and ChainedTokenCredential.
I’m using package version :Azure.Identity v1.20.0
DefaultAzureCredential
This class is a pre-built chain covering the most common authentication methods. When using DefaultAzureCredential to acquire a token, the class attempts to acquire a token via each of the below credentials, in the following order, stopping when one provides a token:
- EnvironmentCredential
- WorkloadIdentityCredential
- ManagedIdentityCredential
- VisualStudioCredential
- VisualStudioCodeCredential (enabled by default for SSO with VS Code on supported platforms when Azure.Identity.Broker is installed)
- AzureCliCredential
- AzurePowerShellCredential
- AzureDeveloperCliCredential
- InteractiveBrowserCredential (not included by default; can use brokered authentication if Azure.Identity.Broker is installed)
source: DefaultAzureCredential Class (Azure.Identity) — Azure for .NET Developers | Microsoft Learn
DefaultAzureCredential with Exclusion
DefaultAzureCredential also supports options that allow you to exclude credentials from evaluation. This is useful if you don’t want to use certain credentials, for example, when running my function locally, I don’t want to use the VisualStudio or VisualStudioCode credential as I prefer AzureCliCredential.
new DefaultAzureCredential(
new DefaultAzureCredentialOptions
{
ExcludeVisualStudioCodeCredential = true,
ExcludeVisualStudioCredential = true
});
ChainedTokenCredential
In some cases, you will know exactly which credentials you want to use. ChainedTokenCredential is very useful in such cases. It evaluates only the credentials you explicitly specify, in the order provided. I use this locally. For example, below I choose to use only CLI and VS credentials when my function is running locally.
new ChainedTokenCredential(
new AzureCliCredential(),
new VisualStudioCredential()
));
Why Managed Identity fails locally but works in Azure
DefaultAzureCredential attempts ManagedIdentityCredential (which is unavailable locally — IMDS timeout) and falls through to developer credentials — VS, CLI, etc (refer the table above). The first credential in the chain that can successfully acquire a token is used.
Note: DefaultAzureCredential evaluates
EnvironmentCredential, then WorkloadIdentityCredential,
followed by ManagedIdentityCredential.
There is no native way to emulate or impersonate a managed identity locally. IMDS (169.254.169.254) is a hypervisor-level endpoint that only exists on Azure compute. It is physically not present on your laptop. The common alternative would be to use a service principal with similar privileges as the UAMI to test your function.
In Azure: The chain gets to ManagedIdentityCredential, IMDS responds, token acquired. Everything below it never runs.
Locally: IMDS doesn’t exist, so ManagedIdentityCredential times out and falls through.
When DefaultAzureCredential is used, the evaluation would like this (assuming none of the credentials are able to provide a token) —
//When running locally there is no IMDS to supply managed identity token
//Assuming VS and other credentials don’t have access to the resource.
//This is how DefaultAzureCredentials evaluates the chain
EnvironmentCredential → skipped (env vars not set)
WorkloadIdentityCredential → skipped (not configured)
ManagedIdentityCredential → unavailable/failure (no IMDS endpoint locally)
VisualStudioCredential → failed
VisualStudioCodeCredential → failed
AzureCliCredential → failed
AzurePowerShellCredential → failed
AzureDeveloperCliCredential → failed
InteractiveBrowserCredential → Not included by default (must be explicitly enabled)
How to configure for local debugging
One approach is to use a factory that returns different credential implementations depending on the execution environment.
For example, when the environment is local, a factory can return a DefaultAzureCredential where you can exclude Visual Studio and Visual Studio Code credentials if you favour AzureCliCredential. Or, better still, if you want to use only Azure CLI or VS credentials, it can return a ChainedTokenCredential with just those two credentials, as shown below
new ChainedTokenCredential(
new AzureCliCredential(),
new VisualStudioCredential()
)
When the environment is Azure, it can just return a DefaultAzureCredential instance or a ChainedTokenCredential as discussed earlier if you are sure about the credential you want to use. If you want to use a specific credential, it can be used directly without DefaultAzureCredential or ChainedTokenCredential. For example, here I’m using a specific credential class —
new ManagedIdentityCredential(
ManagedIdentityId.FromUserAssignedClientId(«your-uami-ClientId»)))
A sample flow will look as below when you use a ChainedTokenCredential as shown previously —
AzureCliCredential → acquire token - SUCCESS
VisualStudioCredential → skipped
Notice that only the two credentials mentioned in the ChainedTokenCredential chain are evaluated.
AZURE_CLIENT_ID influence in identity selection
Many Azure resources can have both System Assigned Managed Identity (SAMI) and User Assigned Managed Identity (UAMI). It is crucial to understand how the AZURE_CLIENT_ID environment variable influences how Azure SDK authentication selects a managed identity. This is not always obvious, and I could not find it clearly documented anywhere
AZURE_CLIENT_ID set?
│
├── YES
│ │
│ └── Which credential in code?
│ │
│ ├── DefaultAzureCredential()
│ │ └── ✅ UAMI (via AZURE_CLIENT_ID)
│ │
│ ├── ManagedIdentityCredential(id)
│ │ └── ✅ UAMI (via explicit id, ignores AZURE_CLIENT_ID)
│ │
│ └── ManagedIdentityCredential()
│ └── ⚠️ SAMI (ignores AZURE_CLIENT_ID)
│
└── NO
│
└── Which credential in code?
│
├── DefaultAzureCredential()
│ └── ⚠️ SAMI
│
├── ManagedIdentityCredential(id)
│ └── ✅ UAMI (via explicit id)
│
└── ManagedIdentityCredential()
└── ⚠️ SAMI (no id provided)
Key rule: ManagedIdentityCredential() does not use
AZURE_CLIENT_ID to select a user-assigned managed identity.
Only DefaultAzureCredential does
Note: the flowchart assumes a System Assigned Managed Identity is present. In cases where SAMI is absent and no UAMI is explicitly provided, token acquisition will fail
Authentication between a Function App and its AzureWebJobsStorage is an independent flow not covered by the flowchart above. See [Using Managed Identity for Function App Authentication with its Storage Account] for a detailed walkthrough.
Summary
DefaultAzureCredential is environment-aware by design — the same code uses managed identity in Azure and falls through to developer credentials locally. This means local failures don’t always predict Azure failures, and the identity that succeeds locally may be in a different tenant than your Azure resources. For local testing against tenant-specific resources, ensure az login --tenant <tenant-id> is used explicitly, not just any az login. To test managed identity behaviour you must deploy, or substitute a service principal with matching roles via EnvironmentCredential.
Reference — Authentication best practices with the Azure Identity library for .NET — .NET | Microsoft Learn