azure-identity
azure-identity is the token layer that every other Azure SDK package for Python plugs into. It gives you credential objects that know how to obtain a Microsoft Entra ID access token in a specific way: from environment variables, from a managed identity on an Azure host, from a Kubernetes workload identity file, from a service principal secret or certificate, or from whatever you last signed in with using the Azure CLI. Every Azure client takes one of these objects in its constructor and calls get_token when it needs to. The headline class, DefaultAzureCredential, tries a fixed chain of the others in order so the same code can run on your laptop and in production without a branch.
The correct and effectively mandatory way to authenticate Azure SDK clients from Python, and managed identity support here is the main reason production code can hold zero secrets. Use DefaultAzureCredential while developing, then pin production to the one credential you actually intend to use.
Use it if
- You call any Azure SDK client from Python (Key Vault, Blob Storage, Service Bus, Cosmos, resource management): they all accept a TokenCredential and this is the package that provides one
- You run on App Service, Functions, Container Apps, AKS, or a VM and want managed identity so there is no secret in your config at all
- You want local development to authenticate as the developer via az login while the deployed build authenticates as its workload identity, from the same source file
- You need Entra tokens outside the SDK too: get_bearer_token_provider hands Azure OpenAI clients a refreshing token, and get_token covers direct REST calls
- You plan to ship DefaultAzureCredential to production without narrowing it. It probes environment variables, workload identity, managed identity, the shared token cache, VS Code, the Azure CLI, Azure PowerShell, the Azure Developer CLI, and the broker, and takes whichever answers first. A machine with a stale az login authenticates as a human, permissions differ from the managed identity you tested with, and the bug only reproduces on that one host
- Startup latency matters. Every credential the chain skips still costs a probe, and the managed identity attempt waits on an IMDS timeout when there is no metadata endpoint, so an unconstrained chain adds seconds to cold starts
- You need Azure AD B2C: the README states plainly that this library does not support it
- You want one token from one endpoint and nothing else. The dependency chain is azure-core, msal, msal-extensions, and cryptography; msal alone, or on an Azure host a plain HTTP call to the IMDS endpoint, is a much smaller surface
- Your container image lacks a prebuilt cryptography wheel, which happens on unusual architectures and some hardened base images. That turns a one-line pip install into a build that needs a Rust toolchain
- You expect async to be free. The credentials in azure.identity.aio need a separate transport install such as aiohttp, and each one must be closed or awaited as a context manager, otherwise you leak sessions on every request that builds a credential
Setup reality
pip install azure-identity needs Python 3.9 or newer and brings azure-core, msal, msal-extensions, cryptography, and typing-extensions with it. The install is quick; the environment is the work. Locally you first sign in with az login, azd auth login, or VS Code, and DefaultAzureCredential picks whichever it finds, so two developers can get different identities from identical code. For a user-assigned managed identity you must pass the client id explicitly, because the default asks for the system-assigned one and a host with several identities returns a token you did not want. Sovereign clouds need the authority argument or AZURE_AUTHORITY_HOST. Async needs pip install aiohttp on top. When something fails you get a single ClientAuthenticationError whose message concatenates the failure of every credential in the chain, which is long, and the useful line is rarely the first. Tokens are cached in memory by default and only persist to disk if you opt in with TokenCachePersistenceOptions.
Patterns
Authenticate an SDK clientdefault-credential
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential()
client = SecretClient(
vault_url="https://my-vault.vault.azure.net",
credential=credential,
)
secret = client.get_secret("db-password")Build one credential and share it across clients. It caches tokens in memory, so a new instance per request throws away the cache and hits Entra ID again.
Narrow the chain for productionconstrain-chain
# option 1: environment switch, no code change
# AZURE_TOKEN_CREDENTIALS=prod (deployed credentials only)
# AZURE_TOKEN_CREDENTIALS=dev (developer tools only)
# AZURE_TOKEN_CREDENTIALS=managedidentitycredential
# option 2: exclude explicitly
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential(
exclude_cli_credential=True,
exclude_powershell_credential=True,
exclude_developer_cli_credential=True,
exclude_visual_studio_code_credential=True,
)This is the single highest-value change to make before deploying. It removes the class of incident where a container inherits a developer identity, and it cuts the probe timeouts out of cold start.
Use a specific managed identityuser-assigned-managed-identity
from azure.identity import ManagedIdentityCredential
# user-assigned: the client id is required
credential = ManagedIdentityCredential(
client_id="11111111-2222-3333-4444-555555555555"
)
# system-assigned: no arguments
# credential = ManagedIdentityCredential()On a host with more than one user-assigned identity, omitting client_id gets you a token for whichever the platform considers default, which typically surfaces as a 403 from the target service rather than an auth error.
Authenticate a service principal with a secretservice-principal
from azure.identity import ClientSecretCredential
credential = ClientSecretCredential(
tenant_id=os.environ["AZURE_TENANT_ID"],
client_id=os.environ["AZURE_CLIENT_ID"],
client_secret=os.environ["AZURE_CLIENT_SECRET"],
)Setting those same three variables and using EnvironmentCredential or DefaultAzureCredential gets you the identical result with no secret in the code path. Prefer managed identity or a certificate where the platform allows it.
Authenticate a pod with workload identity on AKSworkload-identity
from azure.identity import WorkloadIdentityCredential
credential = WorkloadIdentityCredential(
tenant_id=os.environ["AZURE_TENANT_ID"],
client_id=os.environ["AZURE_CLIENT_ID"],
token_file_path=os.environ["AZURE_FEDERATED_TOKEN_FILE"],
)Those three variables are injected by the AKS workload identity webhook, so DefaultAzureCredential finds this path on its own. Instantiate it directly when you want a failure to be loud instead of falling through to the next credential.
Build your own fallback orderchained-credential
from azure.identity import (
ChainedTokenCredential,
ManagedIdentityCredential,
AzureCliCredential,
)
credential = ChainedTokenCredential(
ManagedIdentityCredential(client_id=os.environ["UAMI_CLIENT_ID"]),
AzureCliCredential(),
)Two entries you chose beats the ten-step default chain: the order is explicit, the failure message is short, and there is no path to an identity you did not intend.
Use async credentials correctlyasync-credential
from azure.identity.aio import DefaultAzureCredential
from azure.keyvault.secrets.aio import SecretClient
async with DefaultAzureCredential() as credential:
async with SecretClient(vault_url, credential) as client:
secret = await client.get_secret("db-password")azure.identity.aio needs an async transport installed separately, usually aiohttp. Credentials hold an HTTP session, so close them or use the context manager; creating one per request without closing leaks connections.
Fetch a raw token for your own HTTP callget-token-directly
import requests
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
token = credential.get_token("https://management.azure.com/.default")
resp = requests.get(
"https://management.azure.com/subscriptions?api-version=2022-12-01",
headers={"Authorization": f"Bearer {token.token}"},
)Scopes end in /.default for application permissions and the resource must match the API you are calling. token.expires_on is a POSIX timestamp; the credential caches internally, so calling get_token again is cheap rather than a new network round trip.
Authenticate Azure OpenAI without a keybearer-token-provider
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import AzureOpenAI
token_provider = get_bearer_token_provider(
DefaultAzureCredential(),
"https://cognitiveservices.azure.com/.default",
)
client = AzureOpenAI(
azure_endpoint="https://my-resource.openai.azure.com",
azure_ad_token_provider=token_provider,
api_version="2024-10-21",
)The provider is a callable that returns a fresh token string on each call, so refresh is handled for you. The identity still needs the Cognitive Services OpenAI User role on the resource.
Tell a missing credential from a rejected oneerror-handling
from azure.identity import CredentialUnavailableError
from azure.core.exceptions import ClientAuthenticationError
try:
token = credential.get_token("https://vault.azure.net/.default")
except CredentialUnavailableError:
# nothing to try with: no env vars, no signed-in CLI, no IMDS
raise
except ClientAuthenticationError as e:
# Entra ID said no: wrong tenant, expired secret, no consent
log.error("auth failed: %s", e.message)
raiseCredentialUnavailableError is what lets the chain move on to the next credential, so a chained credential only raises it when every link was unavailable. ClientAuthenticationError from a chain concatenates each link's message, and the actionable one is usually near the end.
Persist the token cache across restartspersistent-token-cache
from azure.identity import (
InteractiveBrowserCredential,
TokenCachePersistenceOptions,
)
credential = InteractiveBrowserCredential(
cache_persistence_options=TokenCachePersistenceOptions(name="my-cli"),
)Caching is in memory only unless you opt in like this. It is worth it for CLI tools so users do not sign in on every invocation, and it uses the OS keyring, which needs a working secret service on headless Linux.
Target a non-public cloudsovereign-cloud
from azure.identity import AzureAuthorityHosts, DefaultAzureCredential
credential = DefaultAzureCredential(
authority=AzureAuthorityHosts.AZURE_GOVERNMENT
)
# or, for every credential at once:
# AZURE_AUTHORITY_HOST=https://login.partner.microsoftonline.cnCredentials backed by a developer tool, such as AzureCliCredential, ignore this and follow whatever cloud that tool is configured for, so a mixed setup can authenticate against two clouds at once.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| msal | PyPI | You want raw Entra token acquisition without the credential chain abstraction, and you are not using Azure SDK clients that expect a TokenCredential |
| azure-identity-broker | PyPI | You build a Windows desktop app and want brokered sign-in through the Web Account Manager instead of a browser redirect |
| azure-cli | PyPI | Your automation is a script in CI that can shell out to az and read a token from it rather than embedding a credential library |