A managed identity is an Entra ID identity that Azure creates and rotates for you, tied to a resource such as a VM or a web app. Code running on that resource can get tokens without storing any secret. If you are still pasting connection strings into config files, this is the first habit to change.
The two kinds
| System-assigned | User-assigned | |
|---|---|---|
| Lifecycle | Created with the resource, deleted with it | Standalone resource you create and delete |
| Shared | One resource only | Can attach to many resources |
| Good for | A single app on a single resource | Scale sets, several apps needing the same access, access granted before the resource exists |
| Role assignments survive a rebuild? | No, the identity is new | Yes |
A simple rule: start with system-assigned for one-off resources; use user-assigned when you rebuild resources often or many resources need the same rights.
Step 1: turn it on
System-assigned on a VM:
az vm identity assign -g rg-app -n vm01
az vm show -g rg-app -n vm01 --query identity.principalId -o tsv
User-assigned:
az identity create -g rg-app -n id-app-reader
ID=$(az identity show -g rg-app -n id-app-reader --query id -o tsv)
az vm identity assign -g rg-app -n vm01 --identities $ID
For App Service: az webapp identity assign -g rg-app -n web01.
Step 2: grant it a role
An identity with no role can sign in but can do nothing. Grant the smallest role at the smallest scope.
PID=$(az vm show -g rg-app -n vm01 --query identity.principalId -o tsv)
# read secrets from one Key Vault (RBAC permission model)
KV=$(az keyvault show -n kv-app01 --query id -o tsv)
az role assignment create --assignee-object-id $PID --assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" --scope $KV
# read blobs in one storage account
SA=$(az storage account show -n stapp01 --query id -o tsv)
az role assignment create --assignee-object-id $PID --assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" --scope $SA
Tip
Role assignments can take a few minutes to take effect. A 403 right after granting is normal; wait and retry before you start debugging.
Step 3: use it from code
The Azure Identity libraries pick the managed identity up automatically through DefaultAzureCredential.
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from azure.storage.blob import BlobServiceClient
cred = DefaultAzureCredential() # on a user-assigned identity, pass managed_identity_client_id=...
secret = SecretClient("https://kv-app01.vault.azure.net", cred).get_secret("db-password")
blobs = BlobServiceClient("https://stapp01.blob.core.windows.net", cred)
print([c.name for c in blobs.list_containers()])
On the VM itself you can test with the CLI:
az login --identity # system-assigned
az login --identity --username <client-id> # user-assigned (newer CLI builds also accept --client-id)
az keyvault secret show --vault-name kv-app01 -n db-password --query value -o tsv
Or call the instance metadata endpoint directly to see the raw token request:
curl -s -H Metadata:true \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net"
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
ManagedIdentityCredential authentication unavailable | Identity not enabled, or code is not running on Azure | Enable it; locally, sign in with az login so DefaultAzureCredential uses your account |
| 403 from Key Vault | Vault uses access policies, not RBAC, or no role | Check az keyvault show --query properties.enableRbacAuthorization |
| Works on one VM in a scale set, not another | Identity attached to the VM, not the scale set model | Assign at the scale set level and update instances |
| Wrong identity used | Several user-assigned identities on the resource | Pass the client ID explicitly |
| 403 from storage with a valid token | Control-plane role (Contributor) instead of a data role | Grant a Storage Blob Data * role |
Prevent it next time
- Ban keys in new apps: use Azure Policy to require managed identity where the service supports it, and disable shared key access on storage accounts that no longer need it.
- Use user-assigned identities in infrastructure-as-code so access is granted before the app deploys.
- Review role assignments for identities quarterly; delete user-assigned identities nobody uses.