Azure SDK: What’s new in the Azure Identity August 2020 General Availability Release

This post has been republished via RSS; it originally appeared at: Microsoft Developer Blogs.

Since we shipped the first Azure Identity library preview in June 2019, it has been a vital part of building Azure cloud solutions. We have received great feedback from our development community and have added new features and have fixed many bugs. However, most of the changes have been in preview in the past few months. Today, we are proud to share the stable release in .NET, Java, Python, and JavaScript/TypeScript with you. This blog will give you a brief introduction to what we are bringing in this release.

In this release, we have added support for more environments and developer platforms, without compromising the simplicity of the DefaultAzureCredential class. It's now easier than ever to authenticate your cloud application on your local workstation, with your choice of IDE or developer tool. When the application is deployed to Azure, you are given more control and insights on how your application is authenticated.

Getting Started

Use the links below to find the August release of each language:

DefaultAzureCredential Updates

In the Azure Identity November 2019 release, DefaultAzureCredential supported reading credentials from environment variables, Managed Identity, Windows shared token cache, and interactively in the browser (for .NET & Python), in that order. In this new release, DefaultAzureCredential is much more powerful, supporting a set of new environments in the following order (a merged list of all languages):

default azure credential flow

  • Environment - The DefaultAzureCredential will read account information specified via environment variables and use it to authenticate.
  • Managed Identity - If the application is deployed to an Azure host with Managed Identity enabled, the DefaultAzureCredential will authenticate with that account.
  • Shared Token Cache (updated, .NET, Java, Python only) - Shared token cache is now also supported on Mac OS and Linux, in addition to Windows. If the developer has authenticated via tools that write to the shared token cache, the DefaultAzureCredential will authenticate with that account.
  • IntelliJ (new, Java only) - If the developer has authenticated via Azure Toolkit for IntelliJ, the DefaultAzureCredential will authenticate with that account.
  • Visual Studio (new, .NET only) - If the developer has authenticated via Visual Studio, the DefaultAzureCredential will authenticate with that account.
  • Visual Studio Code (new) - If the developer has authenticated via the Visual Studio Code Azure Account extension, the DefaultAzureCredential will authenticate with that account.
  • Azure CLI (new) - If the developer has authenticated an account via the Azure CLI az login command, the DefaultAzureCredential will authenticate with that account.
  • Interactive (.NET, Python only) - If enabled the DefaultAzureCredential will interactively authenticate the developer via the current system's default browser.

Using the DefaultAzureCredential remains the same as the previous releases:

// .NET
var client = new SecretClient(new Uri(keyVaultUrl), new DefaultAzureCredential());
// Java
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();

SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl(keyVaultUrl)
    .credential(credential)
    .buildClient();
// JavaScript
const client = new SecretClient(keyVaultUrl, new DefaultAzureCredential());
# Python
client = SecretClient(vault_url, DefaultAzureCredential())

More Credential Types

Not only is the DefaultAzureCredential updated to support these environments, you can also pick the specific credential to use. Here are the list of credentials grouped by usage types:

Authenticating Azure Hosted Applications

credential usage
DefaultAzureCredential provides a simplified authentication experience to quickly start developing applications run in the Azure cloud
ChainedTokenCredential allows users to define custom authentication flows composing multiple credentials
ManagedIdentityCredential authenticates the managed identity of an Azure resource
EnvironmentCredential authenticates a service principal or user via credential information specified in environment variables

Authenticating Service Principals

credential usage
ClientSecretCredential authenticates a service principal using a secret
ClientCertificateCredential authenticates a service principal using a certificate

Authenticating Users

credential usage
InteractiveBrowserCredential interactively authenticates a user with the default system browser
DeviceCodeCredential interactively authenticates a user on devices with limited UI
UsernamePasswordCredential authenticates a user with a username and password
AuthorizationCodeCredential authenticate a user with a previously obtained authorization code

Authenticating via Development Tools

credential usage
VisualStudioCredential authenticate in a development environment with Visual Studio
IntelliJCredential authenticate in a development environment with the account in Azure Toolkit for IntelliJ
VisualStudioCodeCredential authenticate in a development environment with Visual Studio Code
AzureCliCredential authenticate in a development environment with the Azure CLI

Here are the examples to use VisualStudioCodeCredential in JavaScript/TypeScript and Python:

// JavaScript
const client = new SecretClient(keyVaultUrl, new VisualStudioCodeCredential());
# Python
client = SecretClient(vault_url, VisualStudioCodeCredential())

Here is an example to use VisualStudioCredential in .NET:

var client = new SecretClient(new Uri(keyVaultUrl), new VisualStudioCredential());

You can learn about how to configure your Visual Studio for this credential in Authenticating via Visual Studio.

Here is an example to use IntelliJCredential in Java on Windows:

IntelliJCredential intelliJCredential = new IntelliJCredentialBuilder()
    .keePassDatabasePath("C:\Users\user\AppData\Roaming\JetBrains\IdeaIC2020.1\c.kdbx")
    .build();

SecretClient client = new SecretClientBuilder()
    .vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
    .credential(intelliJCredential)
    .buildClient();

The keePassDatabasePath setter is not required on Mac OS or Linux. You can learn about how to configure your IntelliJ IDEA for this credential in Sign in Azure Toolkit for IntelliJ for IntelliJCredential.

You can learn about how to configure your Visual Studio Code for this credential in Sign in Visual Studio Code Azure Account Extension for VisualStudioCodeCredential.

Note: All credential implementations in the Azure Identity library are threadsafe, and a single credential instance can be used by multiple service clients.

More Configurability

User-assigned Managed Identity

You can configure the client ID for a user managed identity:

// .NET
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = userAssignedClientId });

var client = new SecretClient(new Uri(keyVaultUrl), credential);
// Java
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder()
    .managedIdentityClientId("<MANAGED_IDENTITY_CLIENT_ID>")
    .build();

SecretClient client = new SecretClientBuilder()
    .vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
    .credential(defaultCredential)
    .buildClient();
// JavaScript
var credential = new DefaultAzureCredential({ managedIdentityClientId: userAssignedClientId });

const client = new SecretClient(keyVaultUrl, credential);
# Python
# Set environment variable AZURE_CLIENT_ID=<MANAGED_IDENTITY_CLIENT_ID>
client = SecretClient(vault_url, DefaultAzureCredential())

Authenticate to a Specific Tenant

By default, the credentials will authenticate a user account to its home tenant. You can configure specifically which tenant to authenticate to on all the credentials. This is useful if you are authenticating to a guest tenant, or your application is multi-tenant enabled.

// .NET
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { VisualStudioTenantId = contosoTenantId });

var client = new SecretClient(new Uri(keyVaultUrl), credential);
// Java
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder()
    .tenantId("<CONTOSO_TENANT_ID>")
    .build();

SecretClient client = new SecretClientBuilder()
    .vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
    .credential(defaultCredential)
    .buildClient();
// JavaScript
var credential = new DefaultAzureCredential({ tenantId: contosoTenantId });

const client = new SecretClient(keyVaultUrl, credential);
# Python
client = SecretClient(vault_url, DefaultAzureCredential(visual_studio_code_tenant_id=contoso_tenant_id))

Build a Custom Credential Chain

DefaultAzureCredential uses a credential chain internally to attempt authentication with multiple credentials. Each credential in the Azure Identity throws CredentialUnavailableException if it cannot find the required environment to authenticate. If it can find the environment but fails to authenticate, it will throw a different type of exception. The chain skips all the CredentialUnavailableExceptions in the chain, until a token is acquired, or a different type of exception is thrown.

We can build our own ChainedTokenCredential once we understand this logic. For example, if you would like to use Azure CLI for authentication on your workstation, and system-assigned managed identity when your application is deployed to Azure:

// .NET
// authenticate using managed identity if it is available otherwise use the Azure CLI to auth
var credential = new ChainedTokenCredential(new ManagedIdentityCredential(), new AzureCliCredential());

var eventHubProducerClient = new EventHubProducerClient("myeventhub.eventhubs.windows.net", "myhubpath", credential);
// Java
ChainedTokenCredential credential = new ChainedTokenCredentialBuilder()
    .addFirst(new ManagedIdentityCredentialBuilder().build())
    .addLast(new AzureCliCredentialBuilder().build())
    .build();

SecretClient client = new SecretClientBuilder()
    .vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
    .credential(defaultCredential)
    .buildClient();
// JavaScript
// When an access token is requested, the chain will try each
// credential in order, stopping when one provides a token
const firstCredential = new ManagedIdentityCredential();
const secondCredential = new AzureCliCredential();
const credentialChain = new ChainedTokenCredential(firstCredential, secondCredential);

const { KeyClient } = require("@azure/keyvault-keys");
const client = new KeyClient(vaultUrl, credentialChain);
# Python
managed_identity = ManagedIdentityCredential()
azure_cli = AzureCliCredential()
credential_chain = ChainedTokenCredential(managed_identity, azure_cli)

client = EventHubClient(host, event_hub_path, credential_chain)

Other Improvements

Logging Improvements

We are printing more information in our logs for easier diagnostics. Using the Java library as an example, with the following code:

// Java
DefaultAzureCredential defaultCredential = new DefaultAzureCredentialBuilder().build();

SecretClient client = new SecretClientBuilder()
    .vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
    .credential(defaultCredential)
    .buildClient();

client.getSecret("{SECRET_NAME}");

When run locally on a workstation with Visual Studio Code installed, the following log will be printed:

[main] INFO com.azure.security.keyvault.secrets.SecretAsyncClient - Retrieving secret - the-secret
[reactor-http-nio-1] ERROR com.azure.identity.EnvironmentCredential - EnvironmentCredential authentication unavailable. Environment variables are not fully configured.
[reactor-http-nio-1] INFO com.azure.identity.DefaultAzureCredential - Azure Identity => Attempted credential EnvironmentCredential is unavailable.
[reactor-http-nio-1] ERROR com.azure.identity.implementation.IdentityClient - ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, connect timed out.
[reactor-http-nio-1] ERROR com.azure.identity.ManagedIdentityCredential - Azure Identity => ERROR in getToken() call for scopes [https://vault.azure.net/.default]: ManagedIdentityCredential authentication unavailable. Connection to IMDS endpoint cannot be established, connect timed out.
[reactor-http-nio-1] INFO com.azure.identity.DefaultAzureCredential - Azure Identity => Attempted credential ManagedIdentityCredential is unavailable.
[ForkJoinPool.commonPool-worker-19] ERROR com.azure.identity.SharedTokenCacheCredential - Azure Identity => ERROR in getToken() call for scopes [https://vault.azure.net/.default]: SharedTokenCacheCredential authentication unavailable. No accounts were found in the cache.
[ForkJoinPool.commonPool-worker-19] INFO com.azure.identity.DefaultAzureCredential - Azure Identity => Attempted credential SharedTokenCacheCredential is unavailable.
[ForkJoinPool.commonPool-worker-19] ERROR com.azure.identity.implementation.IntelliJCacheAccessor - IntelliJ Authentication not available. Please log in with Azure Tools for IntelliJ plugin in the IDE.
[ForkJoinPool.commonPool-worker-19] ERROR com.azure.identity.IntelliJCredential - Azure Identity => ERROR in getToken() call for scopes [https://vault.azure.net/.default]: IntelliJ Authentication not available. Please log in with Azure Tools for IntelliJ plugin in the IDE.
[ForkJoinPool.commonPool-worker-19] INFO com.azure.identity.DefaultAzureCredential - Azure Identity => Attempted credential IntelliJCredential is unavailable.
[ForkJoinPool.commonPool-worker-5] INFO com.azure.identity.VisualStudioCodeCredential - Azure Identity => getToken() result for scopes [https://vault.azure.net/.default]: SUCCESS
[ForkJoinPool.commonPool-worker-5] INFO com.azure.identity.DefaultAzureCredential - Azure Identity => Attempted credential VisualStudioCodeCredential returns a token
[reactor-http-nio-3] INFO com.azure.security.keyvault.secrets.SecretAsyncClient - Retrieved secret - the-secret

When run in an Azure environment with managed identity available, the following log will be printed:

[main] INFO com.azure.security.keyvault.secrets.SecretAsyncClient - Retrieving secret - secret
[reactor-http-epoll-1] INFO com.azure.identity.ManagedIdentityCredential - Azure Identity => Managed Identity environment: MSI_ENDPOINT
[reactor-http-epoll-1] INFO com.azure.identity.ManagedIdentityCredential - Azure Identity => getToken() result for scopes [https://vault.azure.net/.default]: SUCCESS
[reactor-http-epoll-1] INFO com.azure.security.keyvault.secrets.SecretAsyncClient - Retrieved secret - secret

You will be able to tell which credentials have been attempted, what are the reasons for skipping them, and which credential is eventually picked to complete the authentication.

When using EnvironmentCredential, you will also be able to tell what environment variables are missing from the log. With the following code:

// Java
EnvironmentCredential credential = new EnvironmentCredentialBuilder().build();

SecretClient client = new SecretClientBuilder()
    .vaultUrl("https://{YOUR_VAULT_NAME}.vault.azure.net")
    .credential(credential)
    .buildClient();

client.getSecret("{SECRET_NAME}");

and AZURE_CLIENT_ID and AZURE_TENANT_ID environment variables set, the SDK will print the following log:

[main] ERROR com.azure.identity.EnvironmentCredential - Azure Identity => ERROR in EnvironmentCredential: Failed to create a ClientSecretCredential or ClientCertificateCredential. Missing required environment variable either AZURE_CLIENT_SECRET or AZURE_CLIENT_CERTIFICATE_PATH
[main] ERROR com.azure.identity.EnvironmentCredential - Azure Identity => ERROR in EnvironmentCredential: Failed to determine an authentication scheme based on the available environment variables. Please specify AZURE_TENANT_ID and AZURE_CLIENT_SECRET to authenticate through a ClientSecretCredential; AZURE_TENANT_ID and AZURE_CLIENT_CERTIFICATE_PATH to authenticate through a ClientCertificateCredential; or AZURE_USERNAME and AZURE_PASSWORD to authenticate through a UserPasswordCredential.

Proactive Token Refresh

In previous versions of the Azure Identity libraries, the tokens returned have been modified by us to leave a 2-minute buffer. The isExpired() method on them will return true 2 minutes before when the token will actually expire. The token policy will then refresh it. This is to accommodate the delay between the token acquisition time to the time Azure services receive the request. This is especially useful when the application is under high load - in those situations, an authenticated request carrying a token in its header could be queued up outside the HTTP connection pool for a prolonged period of time. When the request gets finally sent to Azure, the user only finds out the token has expired.

There are also community feedback on making their cloud applications more robust against AAD down times. If AAD is down for a few minutes, they would like the Azure Identity library to offer a way for them to proactively get a new token - so that they always have a more up-to-date token and not wait until the last minute to refresh, only to find out AAD is down.

Thus, the August release of the .NET, Java, and Python libraries are now equipped with a proactive token refresh design (JavaScript/Typescript support is coming in a future release). 5 minutes before the token expiry, when a request comes in, the BearerTokenAuthenticationPolicy will attempt a token refresh - and if this refresh fails (e.g. AAD is unresponsive), the previously cached token will be attached to the request to be sent on Azure. The next request coming in after a 30-second timeout will trigger another token refresh attempt, and again - if this fails, the previously cached token will be attached. This continues until the previously cache token also expires, and only until when will the library throw the authentication error.

All these behaviors are already enabled by default and no user configuration is needed.

AzureAuthorityHosts

When creating a credential, you can specify the authority host the credential should connect to when acquiring a token. We are now adding a new Type AzureAuthorityHost with a set of known authority hosts in Azure. For example, to use Azure Germany cloud:

// .NET
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions { AuthorityHost = AzureAuthorityHosts.AzureGermany });

var client = new SecretClient(new Uri(keyVaultUrl), credential);
// Java
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder()
    .authorityHost(AzureAuthorityHosts.AZURE_GERMANY)
    .build();

SecretClient secretClient = new SecretClientBuilder()
    .vaultUrl("https://mysecretkeyvault.vault.azure.net")
    .credential(credential)
    .buildClient();
// JavaScript
var credential = new DefaultAzureCredential({ authorityHost: AzureAuthorityHosts.AzureGermany });

const client = new SecretClient(keyVaultUrl, credential);
# Python
client = SecretClient(vault_url, DefaultAzureCredential(authority=AzureAuthorityHosts.AzureGermany))

Community acknowledgements

Community feedback has played an important role in the development of this release. Some features, like proactive token refresh, the idea comes entirely from the community. I'd like to give special thanks to the following community members and many others who has contributed in one way or another to the making of this release. Every time you open an issue, suggest a fix in a pull request, or even hit the "like" button on an issue, helps us in our understanding of what you'd like to see in our libraries.

Cheers!

Azure SDK Blog Contributions

Thank you for reading this Azure SDK blog post! We hope that you learned something new and welcome you to share this post. We are open to Azure SDK blog contributions. Please contact us at azsdkblog@microsoft.com with your topic and we'll get you setup as a guest blogger.

REMEMBER: these articles are REPUBLISHED. Your best bet to get a reply is to follow the link at the top of the post to the ORIGINAL post! BUT you're more than welcome to start discussions here:

This site uses Akismet to reduce spam. Learn how your comment data is processed.