The DefaultAzureCredentials is key for using Azure services, but how exactly does it work and when should you use it? In this post, we’ll break down how it operates, its challenges, and other ways to access Azure services. This guide will help you get a clearer picture of how to handle Azure authentication simply and effectively.
Authenticating to Azure
When an application wants to access resources in Azure such as storage or databases, it needs to authenticate itself. To do this, the application must obtain an access token from Entra ID. This token allows the application to securely access resources in Azure, such as your Azure Storage Account.
Alternative ways to access resources exist, such as using Shared Access Signature (SAS) tokens, but that is beyond the scope of this blog post.
For example, to access Azure BlobStorage, a TokenCredential is required during client creation. This ensures secure access by authorized users. Here’s an example of that:
TokenCredential credentials = ??HowToCreate??;
var client = new BlobServiceClient(new Uri("https://myblogstorage.blob.core.windows.net"),
credentials);
What does the TokenCredential class represent?
The TokenCredentials type in C# is an abstract base class that contains two methods to request an access token:
///
/// Represents a credential capable of providing an OAuth token.
///
public abstract class TokenCredential
{
///
/// Gets an Azure.Core.AccessToken for the specified set of scopes.
///
/// A valid Azure.Core.AccessToken.
public abstract ValueTask GetTokenAsync(TokenRequestContext requestContext,
CancellationToken cancellationToken);
public abstract AccessToken GetToken(TokenRequestContext requestContext,
CancellationToken cancellationToken);
}
Microsoft provides many types that implement this abstract class, including:
var c1 = new AuthorizationCodeCredential(tenantId: "",
clientId: "",
clientSecret: "",
authorizationCode: "");
var c2 = new AzureCliCredential();
var c3 = new AzureDeveloperCliCredential();
var c4 = new AzurePowerShellCredential();
var c5 = new ChainedTokenCredential();
var c6 = new ClientAssertionCredential(tenantId: "",
clientId: "",
assertionCallback: x => Task.FromResult(""));
var c7 = new ClientCertificateCredential(tenantId: "",
clientId: "",
clientCertificatePath: "");
var c8 = new ClientSecretCredential(tenantId: "",
clientId: "",
clientSecret: "");
var c9 = new DefaultAzureCredential();
var c10 = new DeviceCodeCredential();
var c11 = new EnvironmentCredential();
var c12 = new InteractiveBrowserCredential();
var c13 = new ManagedIdentityCredential();
var c14 = new OnBehalfOfCredential(tenantId: "",
clientId: "",
clientSecret: "",
userAssertion: "");
var c15 = new SharedTokenCacheCredential();
var c16 = new UsernamePasswordCredential(username: "",
password: "",
tenantId: "",
clientId: "");
var c17 = new VisualStudioCodeCredential();
var c18 = new VisualStudioCredential();
var c19 = new WorkloadIdentityCredential();
What do all these TokenCredentials do?
Each TokenCredential type employs its own approach to acquiring the access token needed to access services in Azure. Whether searching for credentials in your system or launching a separate program, the key aim is to obtain this token safely and effectively, ensuring smooth and reliable access to Azure.
For example, the EnvironmentCredential type will look for credentials in one of these sets of environment variables:
- ClientSecret based
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_CLIENT_SECRET
- UserName/Password based
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_USERNAME
- AZURE_PASSWORD
- Certificate based
- AZURE_TENANT_ID
- AZURE_CLIENT_ID
- AZURE_CLIENT_CERTIFICATE_PATH
- AZURE_CLIENT_CERTIFICATE_PASSWORD
Then it will use these credentials to try to acquire an access token from Entra ID.
VisualStudioCredential is another type that attempts to launch Microsoft.Asal.TokenService.exe, which is an application located in this folder:
C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\CommonExtensions\Microsoft\Asal\TokenService\
It is an executable that manages token-based authentication for Microsoft services. It’s essential for enabling secure user access to Microsoft Azure and Office 365, and it is also utilized by Visual Studio to authenticate users with Microsoft accounts. This component facilitates secure single sign-on and integrates with Entra ID.
This means that the BlobServiceClient shown before accepts any of the above credentials. For example:
var blobUri = new Uri("https://myblogstorage.blob.core.windows.net");
var client1 = new BlobServiceClient(blobUri, new AzureCliCredential());
var client2 = new BlobServiceClient(blobUri, new AzureDeveloperCliCredential());
var client3 = new BlobServiceClient(blobUri, new EnvironmentCredential());
var client4 = new BlobServiceClient(blobUri, new WorkloadIdentityCredential());
TokenCredentials in practice
We have a problem: depending on where our app executes, we need to use different TokenCredentials, depending on where it runs. This means that our code might look something like this:
TokenCredential credentials = null;
switch (environment)
{
case "dev-visualstudio":
credentials = new VisualStudioCredential();
break;
case "dev-visualstudiocode":
credentials = new VisualStudioCodeCredential();
break;
case "production":
credentials = new ManagedIdentityCredential();
break;
default:
credentials = new AzurePowerShellCredential();
break;
This works, but it’s hard to maintain; especially if we want to add more options in the future, like using Azure CLI or WorkloadIdentity with Kubernetes. We need a simpler approach that can work across different environments.
Introducing DefaultAzureCredential
To solve the above problem, Microsoft introduced the DefaultAzureCredential type.
How does it work?
It contains a predefined list of TokenCredential types, which it calls in a specific sequence. DefaultAzureCredential systematically checks each one on this list until it successfully finds a matching set of credentials in your environment, as shown in the following picture:
By using this credential type instead of the others, you don’t need to worry about having to specify what credentials to use, as shown in this example below:
var client = new BlobServiceClient(
new Uri("https://myblogstorage.blob.core.windows.net"),
new DefaultAzureCredential());
The above code will automatically work well in most environments because it tries to locate a valid set of credentials and then retrieve a valid access token.
What happens if DefaultAzureCredentials can’t find any credentials?
The first thing you notice is that it is slow! It will take 3-8 seconds to execute all the TokenCredentials on a machine without credentials.
An exception is thrown when it can’t find any valid credentials, and you can see the details in the debugger foreach failed attempt:
There is a built-in option to disable specific TokenCredentials, like this:
var options = new DefaultAzureCredentialOptions
{
ExcludeAzureCliCredential = true,
ExcludeVisualStudioCodeCredential = true
//..
};
var cred = new DefaultAzureCredential(options);
...
Caching of the access token in DefaultAzureCredentials
It’s best to create a single instance of DefaultAzureCredential and reuse it throughout your application. This approach is more efficient because it leverages the credential’s internal token cache, reducing the need for repeated authentication calls.
For example:
// Create a shared DefaultAzureCredential instance
var sharedCredential = new DefaultAzureCredential();
// Use this shared instance wherever needed
var client1 = new BlobServiceClient(new Uri("https://example.blob.core.windows.net"),
sharedCredential);
var client2 = new SecretClient(new Uri("https://example.vault.azure.net"),
sharedCredential);
…
What does the access token look like?
A sample access token can look like this:
{
"aud": "https://storage.azure.com/",
"iss": "https://sts.windows.net/567d82a1-7f61-4da2-b955-xxxxxxxxxxxx/",
"iat": 1712047403,
"nbf": 1712047403,
"exp": 1712051303,
"aio": "E2NgYKh17fZg9vvfwCmWxW+7xxxxxx==",
"appid": "9bfc7d58-9841-415c-9b60-xxxxxxxxxxxx",
"appidacr": "1",
"idp": "https://sts.windows.net/567d82a1-7f61-4da2-b955-xxxxxxxxxxxx/",
"oid": "1e7e2541-1297-4e1a-8b15-xxxxxxxxxxxx",
"rh": "0.AQwAoYJ9VmF_ok25VdMkTqbpdoGmBuTU86hCkLxxxxxxxxxxxx.",
"sub": "1e7e2541-1297-4e1a-8b15-xxxxxxxxxxxx",
"tid": "567d82a1-7f61-4da2-b955-xxxxxxxxxxxx",
"uti": "NpJBNp_iWUql96nbQP4xQA",
"ver": "1.0",
"xms_tdbr": "EU"
}
The access token above has a lifetime of 65 minutes, and the library will be in charge of refreshing it when needed.
How do I know which credential was used?
You might have several credentials saved on your computer. Sometimes, this can cause a problem where the wrong one gets used, leading to access issues that can be tricky to figure out. Enabling logging is one approach to knowing which one was used.
But digging through the logs is perhaps not the most optimal approach. Unfortunately, it does not provide any official other way to tell which credential was actually used. However, using some reflection magic, we can extract this information using the following hack:
The above method can then be used like this:
// The double slash is intentional for public cloud.
var scope = new string[] { "https://management.azure.com//.default" };
var tokenRequestContext = new TokenRequestContext(scope);
var credentials = new DefaultAzureCredential();
var token = credentials.GetToken(tokenRequestContext);
TokenCredential? selectedCredential = GetSelectedCredentials(credentials);
Console.WriteLine("selectedCredential " + selectedCredential.GetType().Name);
Console.WriteLine("Received token: " + token.Token);
What are the problems with DefaultAzureCredential?
While it is highly convenient and user-friendly, it also has its drawbacks. A notable issue arises in debugging: pinpointing which one was used becomes challenging if it selects an incorrect or unexpected credential.
What are the alternatives to DefaultAzureCredential?
An alternative is the ChainedTokenCredential type. It functions similarly to DefaultAzureCredential, with the key difference being that it starts with an empty list of TokenCredentials. With ChainedTokenCredential, you can add only the credential types relevant to your needs in the order that works best for you.
This approach can be particularly advantageous for production environments, as it puts you in complete control and limits the credentials to only those you actively use.
For instance, you could set it up like this:
TokenCredential? GetSelectedCredentials(DefaultAzureCredential? credential)
{
// This is a hack to get the selected token from DefaultAzureCredential.
// The goal is to get the value from this private field found inside DefaultAzureCredential:
//private readonly AsyncLockWithValue _credentialLock;
try
{
var credType = credential?.GetType();
if (credType != null)
{
var _credentialLockField = credType.GetField("_credentialLock", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (_credentialLockField != null)
{
var _credentialLock = _credentialLockField.GetValue(credential);
var _credentialLockType = _credentialLock?.GetType();
if (_credentialLockType != null)
{
var _valueField = _credentialLockType.GetField("_value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
if (_valueField != null)
{
var selectedCredential = _valueField.GetValue(_credentialLock);
// Use selectedCredential here
return selectedCredential as TokenCredential;
}
}
}
}
return null;
}
catch (Exception ex)
{
Console.WriteLine("Failed to get selected credential");
return null;
}
}
var cred = new ChainedTokenCredential(new EnvironmentCredential(),
new ManagedIdentityCredential(),
new VisualStudioCodeCredential());
…
Logging and diagnostics
A simple way to get insights into what happens under the hood is to add the following code to the start of your application:
var listener = new AzureEventSourceListener((e, message) =>
{
Console.WriteLine(e.EventSource.Name);
if (e.EventSource.Name.StartsWith("Azure"))
{
Console.WriteLine($"{DateTime.Now} {message}");
}
},
level: EventLevel.Verbose);
var totalTimeSw = new Stopwatch();
totalTimeSw.Start();
This allows you to capture the internal events produced by the Azure libraries, which can give you valuable insights into their inner workings.
Final gotcha
A common issue encountered with these credential helpers revolves around the TenantId. Often, difficulties with these types stem from a missing TenantId. To mitigate this, the TenantId can typically be specified through an optional options object or by setting environment variables. For instance, when using VisualStudioCredential:
var options = new VisualStudioCredentialOptions()
{
TenantId = "xxxxx"
};
var credentials = new VisualStudioCredential(options);
Summary
Writing this blog post has been a great learning experience for me. It’s helped me better understand TokenCredential types, especially about when to use DefaultAzureCredential and ChainedTokenCredential.
I gained deeper insights by downloading the Azure Identity source code from GitHub and turning it into my own class library. With this in place, I could add my own logging and diagnostic code. These additions really helped me get a better grasp of how everything functions.
Feedback, comments, found any bugs?
Let me know if you have any feedback, anything I missed or any bugs/typos. You can find my contact details here.
About the author
Hi, I’m Tore! I have been fascinated by computers since I unpacked my first Commodore VIC-20. Today, I enjoy providing freelance development and developer training services, focusing on ASP.NET Core, IdentityServer, OpenID Connect, Architecture, and Web Security. You can connect with me on LinkedIn and Twitter, and you can find out more about me and my services here, as well as my courses for developers, including my course, Introduction to IdentityServer and OpenID-Connect.
Related posts by me:
Tore’s Newsletter
Be the First to Know! Get notified about my latest blog posts, upcoming presentations, webinars, and more — subscribe today!