Azure Managed Identities: Eliminate Hard-Coded Credentials

Azure managed identities give Azure resources — virtual machines, App Services, Azure Functions, and others — an automatically managed identity in Microsoft Entra ID. That identity can authenticate to any service that supports Entra ID authentication without a single password, certificate, or secret appearing in your code or configuration files.

What a managed identity actually is

When you enable a managed identity on a resource, Azure creates a service principal in your Entra ID tenant and links it to that resource. The difference between a managed identity and a regular service principal is who owns the credential lifecycle. With a normal service principal you create a client secret or certificate, store it somewhere, rotate it on a schedule, and risk it leaking. With a managed identity, Azure generates and rotates the underlying credential automatically — you never see it, you never store it.

Under the hood, the resource contacts the Azure Instance Metadata Service (IMDS) at the link-local address 169.254.169.254 to request a short-lived token. That token is scoped to a specific audience (for example, Key Vault or Azure Storage) and expires within an hour. The Azure SDK handles this token acquisition transparently when you use DefaultAzureCredential or ManagedIdentityCredential.

Once the identity exists, you grant it permissions exactly as you would any other security principal: assign an Azure RBAC role at the appropriate scope. The resource then has exactly the access you granted — nothing more.

Before and after: Key Vault access without and with a managed identity

The most concrete way to understand managed identities is to compare code that uses a hard-coded secret against code that uses a managed identity to read the same Key Vault secret. The following examples use Python with the Azure SDK.

Before: hard-coded client secret (dangerous)

In this pattern, a developer stores the service principal’s client ID and secret in environment variables or, worse, directly in source code. The secret must be rotated manually, and any compromise of the deployment environment exposes the secret permanently until it is regenerated.

import os
from azure.identity import ClientSecretCredential
from azure.keyvault.secrets import SecretClient

# These values must be stored somewhere — environment variables, a config file,
# a CI/CD secret store. Any of those can leak.
tenant_id     = os.environ["AZURE_TENANT_ID"]
client_id     = os.environ["AZURE_CLIENT_ID"]
client_secret = os.environ["AZURE_CLIENT_SECRET"]   # <-- the dangerous part

credential = ClientSecretCredential(
    tenant_id=tenant_id,
    client_id=client_id,
    client_secret=client_secret,
)

vault_url = "https://my-vault.vault.azure.net"
client = SecretClient(vault_url=vault_url, credential=credential)

secret = client.get_secret("database-password")
print(secret.value)

Problems with this approach: the secret can end up in version control, in container image layers, in log output, or in a CI/CD variable that a team member accidentally exports. Rotating it requires updating every deployment that uses it.

After: managed identity (no secrets anywhere)

When your Azure Function or App Service has a managed identity enabled, you replace the ClientSecretCredential with DefaultAzureCredential. The SDK automatically requests a token from IMDS. There is no client secret in the code, the environment, or the deployment pipeline.

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

# DefaultAzureCredential tries managed identity first when running in Azure.
# No tenant ID, client ID, or secret needed.
credential = DefaultAzureCredential()

vault_url = "https://my-vault.vault.azure.net"
client = SecretClient(vault_url=vault_url, credential=credential)

secret = client.get_secret("database-password")
print(secret.value)

The only configuration change outside the code is granting the managed identity the Key Vault Secrets User role on the vault. That role assignment is auditable, revocable, and scoped to exactly what the function needs.

DefaultAzureCredential tries a chain of credential sources in order: environment variables, workload identity, managed identity, Azure CLI, and others. During local development it falls back to your CLI login, so the same code works both locally and in production without modification.

System-assigned and user-assigned identities

There are two types of managed identities. The difference is about lifecycle and reuse.

A system-assigned identity is created when you enable it on a specific resource and deleted automatically when you delete that resource. It has a one-to-one relationship with the resource. You cannot share it with another resource.

A user-assigned identity is a standalone Azure resource that you create independently and then attach to one or more Azure resources. Its lifecycle is separate from any resource it is attached to. You can attach the same user-assigned identity to ten Azure Functions, meaning all ten share a single RBAC assignment rather than requiring ten separate assignments.

For detailed guidance on choosing between the two types, see the system-assigned vs user-assigned identities page.

Which Azure services support managed identities

Managed identities can be assigned to (used as the authenticating principal by) a wide range of compute and integration services, including:

  • Virtual Machines and Virtual Machine Scale Sets
  • Azure App Service and Azure Functions
  • Azure Container Instances and Azure Kubernetes Service (AKS) pods via workload identity
  • Azure Logic Apps
  • Azure Data Factory
  • Azure API Management
  • Azure Spring Apps

Managed identities can authenticate to (be granted access to) any service that accepts Entra ID tokens, including:

  • Azure Key Vault
  • Azure Storage (Blob, Queue, Table)
  • Azure SQL Database and Azure Cosmos DB
  • Azure Service Bus and Azure Event Hubs
  • Azure Resource Manager (for infrastructure automation)
  • Any REST API that accepts Bearer tokens from Entra ID

Enabling a managed identity on a Virtual Machine

You enable a system-assigned identity on a VM with a single CLI command:

az vm identity assign \
  --name my-vm \
  --resource-group my-rg

The output includes the principalId (the object ID of the service principal in Entra ID) and the tenantId. You use the principalId when assigning RBAC roles.

To then grant that VM’s identity the ability to read secrets from a Key Vault:

# Get the principal ID of the VM's managed identity
PRINCIPAL_ID=$(az vm identity show \
  --name my-vm \
  --resource-group my-rg \
  --query principalId \
  --output tsv)

# Get the Key Vault resource ID
VAULT_ID=$(az keyvault show \
  --name my-vault \
  --resource-group my-rg \
  --query id \
  --output tsv)

# Assign the "Key Vault Secrets User" role
az role assignment create \
  --assignee "$PRINCIPAL_ID" \
  --role "Key Vault Secrets User" \
  --scope "$VAULT_ID"

After this, any code running on that VM can call the Key Vault API without credentials, as long as it requests a token scoped to https://vault.azure.net. The SDK handles that automatically.

How the token request works at runtime

When your application calls DefaultAzureCredential().get_token(“https://vault.azure.net/.default”), the SDK sends an HTTP GET to the IMDS endpoint:

GET http://169.254.169.254/metadata/identity/oauth2/token
  ?api-version=2018-02-01
  &resource=https://vault.azure.net
Header: Metadata: true

Azure returns a JSON response containing a short-lived JWT access token. The SDK caches this token until it is close to expiry and then fetches a fresh one. Your application never stores a long-lived credential — the token is valid for approximately one hour and cannot be used for anything outside the resource it was issued for.

This token acquisition is transparent when you use the Azure SDK. You only need to understand it if you are making raw HTTP calls or debugging authentication failures.

Managed identities and the broader identity model

Managed identities sit within the same identity model as users and service principals. They are covered by the same Azure RBAC system. You assign roles, check access with az role assignment list, and audit them through activity logs exactly as you would for any other principal.

The key conceptual difference from human users is that there is no login prompt, no MFA challenge, and no password to forget or share. The identity is the resource. When the resource is gone, so is the identity (for system-assigned) or you explicitly manage the identity’s lifecycle (for user-assigned).

For workloads that cannot use managed identities — for example, on-premises servers or third-party CI/CD systems — see why secrets and keys are dangerous for guidance on safer alternatives.

Common mistakes with managed identities

  1. Granting Owner or Contributor at subscription scope. Teams sometimes give their managed identity a broad role “just to be safe.” This violates the principle of least privilege and means that if the compute resource is compromised, the attacker controls the entire subscription. Always scope the role to the specific resource the identity needs to access.
  2. Not testing locally before deploying. DefaultAzureCredential works locally using your Azure CLI login, but if you have not run az login or your CLI session has expired, you will get an authentication error that looks like a managed identity failure. Separate testing environments from production to avoid confusion.
  3. Forgetting to enable the identity before deploying code. If you deploy code that calls IMDS before the managed identity is enabled on the resource, the call to IMDS fails immediately with a connection refused error. Enable the identity at provisioning time, not as an afterthought.
  4. Using the wrong identity type for shared workloads. Creating ten system-assigned identities and then making ten separate RBAC assignments is operationally painful. Use a user-assigned identity when multiple resources need the same access pattern.
  5. Confusing the principal ID with the client ID. When assigning RBAC roles you use the principalId (the object ID). When making token requests in code (rare with SDK) you may use the clientId. They are different values. Check the output of az vm identity show carefully.

Frequently asked questions

Do managed identities work outside Azure?

No. Managed identities are exclusive to the Azure platform. They rely on the Azure Instance Metadata Service (IMDS), which is only reachable from inside Azure-hosted compute resources. If your code runs on-premises or in another cloud, you need a service principal with a stored credential instead.

Can a managed identity be shared between resources in different subscriptions?

A user-assigned managed identity lives in one subscription and resource group. It can be assigned to resources in a different subscription within the same Microsoft Entra ID tenant. Cross-tenant assignment is not supported.

Is there a cost for managed identities?

There is no direct charge for creating or using a managed identity. You may incur costs for the services the identity accesses (for example, Key Vault API calls), but the identity mechanism itself is free.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.