In this post, let's have a look at the
AZURE_TOKEN_CREDENTIALS environment variable in
Azure.Identity. I only came across it recently, and it has removed something that used to annoy me every single day.
We all know why we should be authenticating to Azure services with Microsoft
Entra ID rather than with keys, connection strings or passwords.
TokenCredential credential = new DefaultAzureCredential();
When I am working on something locally, what I used to do is grant my own Azure account access to the development resource, sign in through Visual Studio or the Azure CLI, and let DefaultAzureCredential pick that identity up.
But this can be a pain when running locally. DefaultAzureCredential is a chain. It attempts credentials
one after the other, in order, and the first one that returns a token
wins. The deployed service credentials (EnvironmentCredential, WorkloadIdentityCredential, ManagedIdentityCredential) come first and then the developer tool credentials like
VisualStudioCredential, AzureCliCredential,
AzureDeveloperCliCredential
etc.
In our local machine, deployed credentials obviously won't work, but still
it will get tried. ManagedIdentityCredential is the worst among them, because it actually goes out to the Azure Instance Metadata Service (IMDS) endpoint at 169.254.169.254 and only gives up once that socket fails or
times out. So every run and every debug session pays for these guaranteed
failures.
Let's look by an example. I am using latest Azure.Identity package as of today.
Here is the basic code.
Let's look by an example. I am using latest Azure.Identity package as of today.
<PackageReference Include="Azure.Identity" Version="1.21.0" />
using Azure.Core; using Azure.Core.Diagnostics; using Azure.Identity; using System.Diagnostics.Tracing; using AzureEventSourceListener listener = new((eventArgs, message) => { // Helper method to print out the provider PrintProvider(eventArgs, message); }, EventLevel.Informational); TokenCredential credential = new DefaultAzureCredential(); // Omitted: some call that exercises the credentials Console.WriteLine("\nDone");
Trying : DefaultAzureCredential Trying : EnvironmentCredential Trying : WorkloadIdentityCredential Trying : ManagedIdentityCredential Trying : VisualStudioCredential Selected : VisualStudioCredential
Done
You can see all the credentials it's trying and it takes unnecessary time.
For a long time my workaround was to branch on the environment, and when running in
Development, switch off the credentials that were never going to work
anyway, using the Exclude prefixed properties on
DefaultAzureCredentialOptions, something like below.
TokenCredential credential = new DefaultAzureCredential(); // Environment check, could be ASPNETCORE_ENVIRONMENT, AZURE_FUNCTIONS_ENVIRONMENT etc. bool isDevelopment = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") == "Development"; if (isDevelopment) { credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ExcludeEnvironmentCredential = true, ExcludeWorkloadIdentityCredential = true, ExcludeManagedIdentityCredential = true, }); }
With this, it will print something like this.
It works, but it's a pain to do this in each project I am working on.
Trying : DefaultAzureCredential Trying : VisualStudioCredential Selected : VisualStudioCredential Done
Since Azure.Identity 1.14.0, all of the above can be replaced with an
environment variable. Set AZURE_TOKEN_CREDENTIALS to dev and
the chain drops every deployed service credential, leaving only the
developer tool ones.
{
"profiles": {
"ConsoleApp1": {
"commandName": "Project",
"environmentVariables": {
"DOTNET_ENVIRONMENT": "Development",
"AZURE_TOKEN_CREDENTIALS": "dev"
}
}
}
}
And the code then goes back to being the single line.
Exactly the same outcome as the Exclude block, with no branching,
nothing to maintain. It is purely configuration.
TokenCredential credential = new DefaultAzureCredential();
One thing to keep in mind is that if AZURE_TOKEN_CREDENTIALS isn't set at all, DefaultAzureCredential quietly falls back to the full chain. So if someone clones the repo without the launch profile, or the variable gets dropped somewhere along the way, you are silently back to where you started. If you would rather fail fast, since Azure.Identity 1.16.0 there is a constructor overload that takes the environment variable name and requires it to be set to a valid value.
TokenCredential credential = new DefaultAzureCredential(DefaultAzureCredential.DefaultEnvironmentVariableName);
Now if it's missing, you get a clear error instead of a silent fallback.
Unhandled exception.
System.InvalidOperationException: Environment variable 'AZURE_TOKEN_CREDENTIALS' is not set or is empty.
And if you typo the value, it throws as well and lists every value it accepts, which is quite handy.
There is a prod value too, which does the opposite and keeps only the
deployed service credentials. And from Azure.Identity 1.15.0 onwards
you can go further and name a single credential, which reduces the chain to just that one. The comparison is case insensitive.
"AZURE_TOKEN_CREDENTIALS": "VisualStudioCredential"
A small feature, but if you have been quietly copying that
Exclude block around for years like I have, it is a life saver.
More read:
Happy Coding.
Regards,
Jaliya