AppAuthentication to Azure.Identity Migration Guidance
When the Microsoft.Azure.Services.AppAuthentication library was first released in fall 2017, it was specifically designed to help mitigate the common and systemic issue of credentials in source code. It introduced a new paradigm for app development that allowed developers to write code once and let AppAuthentication
client library determine how to authenticate based on the application environment - whether
on a developer workstation using a developer's account or deployed in Azure using a managed service identity. Developers could completely avoid directly handling credentials, both simplifying development and improving security by preventing credentials from being accidentally disclosed in source code. Given its simplicity and security benefits,
AppAuthentication
was well received by developers. The NuGet received over 160 million downloads, and it was used in other libraries and frameworks for Azure services, such as the Azure Key Vault Configuration Provider in .NET
Core
and the Microsoft.Azure.ServiceBus SDK.
Released in fall 2019, the Azure.Identity client library is the spiritual successor to the AppAuthentication
library. Azure.Identity has a major advantage over AppAuthentication
with its broader availability in multiple languages that provide consistent design and similar usage across these languages, whereas AppAuthentication
was only available for .NET. In addition to its support of multiple languages, one key design feature of Azure.Identity is its various implementations of the abstract TokenCredential class, of which newer Azure client SDKs
accept. These newer Azure SDKs are easily distinguished by package names and namespaces that start with "Azure", i.e. "Azure.Identity", "Azure.Storage.Blobs". To authenticate, the desired type of TokenCredential object is instantiated and simply passed directly to the
Azure SDK client class. This design gives using Azure.Identity an additional security benefit over using AppAuthentication and older SDKs that require specifying the access token because access tokens do not
need to be directly handled by the application itself. This mitigates the additional risk of access tokens being accidentally disclosed through traces, logs, and other sources.
If you are starting development of a new application, it is strongly recommended to use Azure.Identity
and the new Azure client SDKs. If you have an existing application that uses AppAuthentication and want to use Azure.Identity, the preferred path is to update your application to use the new Azure client SDKs that support accepting TokenCredentials. AppAuthentication is now considered deprecated and there will be no further investment in its development. Using the DefaultAzureCredential
in Azure.Identity
will provide similar functionality to
AzureServiceTokenProvider
in AppAuthentication
, where the authentication provider used will change based on the current environment. If you are using an AppAuthentication
connection string for a specific authentication provider using AppAuthentication
, please see the below table to see how to use the same authentication provider by creating the appropriate TokenCredential in Azure.Identity.
Auth Provider | AppAuthentication Connection String |
Azure.Identity TokenCredential |
---|---|---|
(Default) environment-based | Default - no connection string used | new DefaultAzureCredential()* |
Azure CLI | RunAs=Developer; DeveloperTool=AzureCli |
new AzureCliCredential() |
Visual Studio | RunAs=Developer; DeveloperTool=VisualStudio | new VisualStudioCredential() |
Windows Integrated Authentication | RunAs=CurrentUser | No support |
System-assigned managed identity | RunAs=App | new ManagedIdentityCredential() |
User-assigned managed identity | RunAs=App;AppId=appId | new ManagedIdentityCredential(appId) |
Service principal client certificate | RunAs=App;AppId=appId; KeyVaultCertificateSecretIdentifier=kvIdentifier |
No support |
Service principal client certificate | RunAs=App;AppId=appId;TenantId=tenantId; CertificateThumbprint=thumbprint; CertificateStoreLocation=location |
new EnvironmentCredential()** new ClientCertificateCredential(tenantId, appId, certObjOrFilePath) |
Service principal client certificate | RunAs=App;AppId=appId;TenantId=tenantId; CertificateSubjectName=subject; CertificateStoreLocation=location |
new EnvironmentCredential()** new ClientCertificateCredential(tenantId, appId, certObjOrFilePath) |
Service principal client secret | RunAs=App;AppId=appId;TenantId=tenantId; AppKey=secret |
new EnvironmentCredential()** new ClientSecretCredential(tenantId, appId, secret) |
Note
*
Authentication providers and order is different between AzureServiceTokenProvider and DefaultAzureCredential
**
Need to set environment variables
While Azure.Identity supports most authentication scenarios and providers that AppAuthentication has, there are some scenarios and features that are currently not supported:
Windows integrated authentication provider
System.Data.SqlClient.SqlAuthenticationProvider implementation (SqlAppAuthenticationProvider)
- For Microsoft.Data.SqlClient, see Active Directory Default authentication. This authentication mode provides similar functionality where DefaultAzureCredential is used to obtain access token for authentication to SQL instances.
Directly use certificates in cert store as client credential (using subject name or thumbprint identifier)
Directly use certificates in Key Vault as client credential (using Key Vault certificate secret identifier)
Change authentication provider with environment config (i.e. connection strings in AppAuthentication)
- Limited support in Azure.Identity with DefaultAzureCredential and EnvironmentCredential, see environment variables
Determine auth provider and identity used for environment-based authentication (i.e. AzureServiceTokenProvider.PrincipalUsed property)
- Can determine auth provider used with DefaultAzureCredential in Azure.Identity using AzureEventSourceListener
Below are some examples of migrating from an older Azure client SDK using AppAuthentication to the newer version of the Azure client SDK using Azure.Identity. The code using Azure.Identity is more often than not going to be much more straightforward and simple than the AppAuthentication code
Microsoft.Azure.KeyVault to Azure.Security.KeyVault:
- Using
AppAuthentication
library
AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider();
var client = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(tokenProvider.KeyVaultTokenCallback));
var secretBundle = await client.GetSecretAsync("https://keyvaultname.vault.azure.net/secrets/secretname");
Console.WriteLine(secretBundle.Value);
- Using
Azure.Identity
library
var client = new SecretClient(new Uri("https://keyvaultname.vault.azure.net"), new DefaultAzureCredential());
var secret = client.GetSecret("secretName").Value;
Console.WriteLine(secret.Value);
Microsoft.Azure.Storage.Queues to Azure.Storage.Queues:
- Using
AppAuthentication
library
var tokenProvider = new AzureServiceTokenProvider();
var accessToken = await tokenProvider.GetAccessTokenAsync("https://storageaccountname.queue.core.windows.net");
var tokenCredential = new StorageTokenCredential(accessToken);
var storageCredentials = new StorageCredentials(tokenCredential);
var uri = new StorageUri(new Uri("https://storageaccountname.queue.core.windows.net"));
var client = new CloudQueueClient(uri, storageCredentials);
var queue = client.GetQueueReference("queuename");
queue.AddMessage(new CloudQueueMessage("Microsoft.Azure.Storage.Queues"));
- Using
Azure.Identity
library
QueueClient queueClient = new QueueClient(new Uri("https://storageaccountname.queue.core.windows.net/queuename"), new DefaultAzureCredential());
queueClient.SendMessage("Azure.Storage.Queues");
Access token retrieval
- Using
AppAuthentication
library
var tokenProvider = new AzureServiceTokenProvider();
var accessToken = await tokenProvider.GetAccessTokenAsync(ResourceId);
- Using
Azure.Identity
library
var tokenCredential = new DefaultAzureCredential();
var accessToken = await tokenCredential.GetTokenAsync(
new TokenRequestContext(scopes: new string[] { ResourceId + "/.default" }) { }
);
Note
More information on the .default
scope can be found here.