Azure Managed Identity: System vs User Assigned, RBAC, and Token Fixes
Why Azure Managed Identity Issues Happen
Here's a scenario I've seen play out dozens of times: a developer deploys a new Azure Virtual Machine or App Service, wires up the code to call Azure Storage, and everything looks fine in the portal. Then the application hits production and starts throwing 401 Unauthorized or ManagedIdentityCredential authentication failed errors at runtime. The portal shows a green checkmark. The logs show nothing useful. And the error message itself tells you almost nothing actionable.
I know this is frustrating , especially when it blocks a deployment or causes an on-call incident at 2 a.m. The root cause almost always falls into one of three buckets.
First: the identity was never actually authorized to access the target resource. Creating a managed identity and assigning it to a VM is only half the job. The identity still needs an RBAC role assignment on the storage account, Key Vault, SQL database, or whatever downstream service you're calling. Skipping this step is by far the most common mistake I see, even among experienced Azure engineers.
Second: confusion between system-assigned and user-assigned managed identities. They behave differently, have different lifecycles, and the wrong choice for your architecture can cause silent failures , especially when resources get recycled or redeployed. If you're creating a new VM and the old user-assigned identity was attached to a previous VM that got deleted, your code is trying to authenticate with an identity that no longer has the right assignments.
Third: the token acquisition call itself is broken. This shows up most often when developers call the Instance Metadata Service (IMDS) endpoint directly instead of using the Azure.Identity SDK or MSAL. Manual HTTP calls to http://169.254.169.254/metadata/identity/oauth2/token are fragile, a missing header, wrong API version, or incorrect resource URI will give you a cryptic error that points nowhere useful.
The reason Microsoft's error messages don't help much here is that the token acquisition flow passes through multiple layers, the compute resource, the Entra ID token endpoint, and the downstream resource's authorization check, and the failure can happen at any of those layers while surfacing as a generic 401 at the application level.
The good news: every one of these issues is fixable once you know where to look. This guide walks through the exact steps to get Azure managed identity working correctly, whether you're using a system-assigned identity on a single VM or a user-assigned identity shared across a fleet of services.
The Quick Fix, Try This First
If your application is throwing ManagedIdentityCredential authentication failed or a 401 Unauthorized when accessing an Azure resource, the single most likely cause is a missing RBAC role assignment. Before you dig into anything else, do this check first.
Go to the Azure Portal and open the resource your application is trying to access, for example, a Storage Account or a Key Vault. In the left navigation panel, click Access control (IAM). Then click the Role assignments tab. Look for your managed identity's name in the list.
If it's not there, that's your problem. Click + Add, then Add role assignment. Pick the appropriate role, for Storage, that's typically Storage Blob Data Reader or Storage Blob Data Contributor. On the Members tab, change "Assign access to" to Managed identity, click + Select members, and find your managed identity by name. Save the assignment.
Role assignments can take up to two minutes to propagate. After saving, restart your application or VM, then test again. In most cases, I'd say 70% of the tickets I've seen, this single step resolves the error immediately.
If the identity is already listed in IAM and you're still getting failures, the issue is almost certainly in how the token is being requested from code, or there's a mismatch between the identity type and how the SDK is configured. The steps below cover those scenarios in detail.
The first decision you need to make is which type of Azure managed identity fits your situation. There are two types and they are not interchangeable after the fact.
System-assigned identities are created directly on a single Azure resource, a VM, App Service, Function App, or Logic App. They share the lifecycle of that resource. When the resource is deleted, the identity is deleted automatically. Use this when you have a single, long-lived resource that needs its own isolated identity and you don't plan to ever reassign that identity elsewhere.
User-assigned identities are standalone Azure resources that you create independently and then attach to one or more compute resources. Microsoft's own recommendation is to use user-assigned identities for most production workloads, particularly anywhere resources are recycled, redeployed, or shared. The identity persists even if the VM or App Service instance gets recreated.
To create a user-assigned managed identity, open the Azure Portal and search for Managed Identities in the top search bar. Click + Create. Choose your subscription, resource group, region, and give it a clear name, something like myapp-identity-prod that makes its purpose obvious six months from now. Click Review + create, then Create.
For system-assigned identity, you enable it directly on the resource. On a VM, go to the VM blade, click Identity in the left menu, set the Status toggle to On under the System assigned tab, and click Save. Azure creates the corresponding service principal in Microsoft Entra ID automatically.
After creation, verify the identity appears in the Microsoft Entra ID portal under Enterprise applications with Application type set to Managed Identity. If it's not there within a couple of minutes, refresh and check again, this is a signal the creation didn't complete properly.
If you created a user-assigned managed identity (which I recommend for most setups), the next step is attaching it to the compute resource that will use it. The identity doesn't do anything on its own, it needs to be linked to the VM, App Service, Azure Kubernetes Service node pool, or other supported platform.
For a Virtual Machine: Open the VM in the portal, go to Identity in the left menu, click the User assigned tab, then click + Add. Select your managed identity from the list and click Add. The assignment takes effect immediately, no restart required for the assignment itself, though your running application may need to reinitialize its credential object.
For Azure App Service: Go to your App Service blade, click Identity in Settings, switch to the User assigned tab, click + Add, select your identity, and save. Again, no restart needed for the assignment, but you may want to restart the app to force the Azure.Identity SDK to pick up the new identity context.
For Azure Kubernetes Service: User-assigned managed identities attach at the node pool level or through the Workload Identity feature. If you're using AKS workload identity, the configuration involves annotating the Kubernetes service account and federating it with the managed identity, that's a separate guide, but the identity itself is created the same way.
After assigning, go back to the Identity blade on the resource and confirm the identity is listed. Also note the Client ID of the user-assigned identity, you'll need this in your application code if you have multiple identities attached to the same resource and need to specify which one to use.
This is the step most people skip, and it's why most Azure managed identity errors happen. The identity existing and being assigned to your compute resource doesn't mean it can actually access anything. You need to explicitly grant it permissions on every downstream resource it needs to touch.
Azure uses Role-Based Access Control (RBAC) for this. Every permission grant is a role assignment that ties together three things: who (the managed identity), what role (a set of permissions), and what scope (the specific resource, resource group, or subscription).
Go to the target resource, the Storage Account, Key Vault, Service Bus, Cosmos DB, or SQL Server your application needs to reach. Click Access control (IAM) in the left menu. Click + Add → Add role assignment.
On the Role tab, pick the right built-in role. Here are the ones I see used most often:
- Storage: Storage Blob Data Contributor (read/write) or Storage Blob Data Reader (read only)
- Key Vault: Key Vault Secrets User (read secrets) or Key Vault Certificate User
- Service Bus: Azure Service Bus Data Receiver or Azure Service Bus Data Sender
- Cosmos DB: Cosmos DB Built-in Data Reader or Cosmos DB Built-in Data Contributor
On the Members tab, set "Assign access to" to Managed identity. Click + Select members, filter by your subscription, and choose your identity. Complete the wizard. Role propagation can take 1–2 minutes. After that, your identity should be authorized to access the target resource using Entra ID authentication, no passwords, no connection strings, no secrets in your config files.
One critical note: assigning a role at the resource group or subscription scope gives the identity access to all resources within that scope. Be deliberate about scope, least privilege is the right default here. Assign at the individual resource level unless there's a specific operational reason to go broader.
With the identity created, attached, and authorized, now your code needs to actually use it. The right way to do this is through the Azure.Identity SDK (for .NET, Python, Java, JavaScript) or through MSAL. Do not call the IMDS endpoint directly unless you have a very specific reason, the SDK handles token caching, retry logic, and environment detection automatically.
For .NET using Azure.Identity:
// For system-assigned identity, no configuration needed
var credential = new DefaultAzureCredential();
var client = new BlobServiceClient(new Uri("https://youraccount.blob.core.windows.net"), credential);
// For user-assigned identity, pass the client ID
var credential = new ManagedIdentityCredential(clientId: "your-client-id-here");
var client = new BlobServiceClient(new Uri("https://youraccount.blob.core.windows.net"), credential);
For Python:
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
from azure.storage.blob import BlobServiceClient
# System-assigned
credential = DefaultAzureCredential()
# User-assigned (pass the client ID)
credential = ManagedIdentityCredential(client_id="your-client-id-here")
client = BlobServiceClient(account_url="https://youraccount.blob.core.windows.net", credential=credential)
The DefaultAzureCredential class is convenient but can cause confusion when you have multiple identities. It tries a chain of credential sources in order, environment variables, workload identity, managed identity, Azure CLI, and more. If your compute resource has both a system-assigned and a user-assigned identity, DefaultAzureCredential may not pick the one you expect. When in doubt, use ManagedIdentityCredential explicitly with the clientId of your user-assigned identity.
If everything is configured correctly, the first SDK call to an Azure resource will silently acquire a token from Microsoft Entra ID in the background. You'll never see a login prompt or need to store a secret anywhere in your code or configuration files. That's exactly the point, managed identities eliminate the need to manage credentials entirely.
Once your code is deployed, it's worth verifying that token acquisition is actually working correctly, not just assuming success because the application started. There are two places to check.
Microsoft Entra ID Sign-in Logs: Go to the Azure Portal, open Microsoft Entra ID, then click Monitoring → Sign-in logs. Filter by the name of your managed identity (or its application/client ID). You should see entries with Application type: Managed Identity. A successful sign-in shows Status: Success. A failure will show an error code and reason, for example, AADSTS700016 means the application (identity) was not found in the directory, which usually means the identity was deleted and recreated without updating the assignment.
Azure Activity Log: Go to the target resource (e.g., Storage Account) and click Activity log. This shows all CRUD operations and access events. If your identity is making calls, you'll see entries tied to its service principal. This is especially useful when debugging whether a role assignment took effect.
To test token acquisition directly from within a VM (without deploying your full application), use this PowerShell command from inside the VM:
# Test managed identity token acquisition from within a VM
$response = Invoke-WebRequest `
-Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://storage.azure.com/" `
-Headers @{Metadata="true"} `
-Method GET
$token = ($response.Content | ConvertFrom-Json).access_token
Write-Host "Token acquired. Length: $($token.Length)"
If this returns a token, the identity is properly configured at the infrastructure level and any remaining errors are in your application code or SDK configuration. If it returns a 400 Bad Request or connection refused, the identity is not attached to the VM at all, go back to Step 2.
If you get an error like {"error":"invalid_resource"}, the resource URI in your request is wrong. For Azure Storage it must be exactly https://storage.azure.com/ with a trailing slash. For Key Vault it's https://vault.azure.net (no trailing slash). These small differences matter.
Advanced Troubleshooting for Azure Managed Identity
When the standard steps don't resolve the problem, you're usually dealing with one of the following more specific failure modes. I've grouped them by what you'll see in the logs.
Error: AADSTS700016, Application Not Found in Directory
This means the service principal backing the managed identity either doesn't exist or belongs to a different tenant. It happens most often after a VM is deleted and recreated, if the new VM gets a new system-assigned identity, the old role assignments on your Storage Account or Key Vault are now dangling references pointing at a deleted principal. Go to each target resource's IAM tab and look for role assignments that show "Identity not found" in red. Delete those orphaned assignments and re-add the new identity.
Error: AADSTS50020, User Account from Identity Provider Does Not Exist
This surfaces when a managed identity is trying to access a resource in a different Azure Active Directory tenant than where the identity was created. Managed identities are tenant-scoped, a user-assigned identity in Tenant A cannot authenticate to resources in Tenant B without a cross-tenant federation setup. If your architecture spans tenants (common in multi-customer ISV scenarios or acquisitions), you need to either consolidate to a single tenant or use workload identity federation, not standard managed identities.
Token Works Locally (Azure CLI) but Not in Production VM
This is a DefaultAzureCredential ordering issue. On your local machine, DefaultAzureCredential falls through to your Azure CLI login credentials. In production, it should use the managed identity, but if you have environment variables like AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, or AZURE_TENANT_ID set in your App Service configuration, DefaultAzureCredential tries those first and fails if the values are stale. Clear those environment variables from production App Service configuration settings and let the managed identity take over.
Role Assignment Exists But Still Getting 403
Check the scope carefully. A role assignment at a resource group level should work, but some Azure services (Key Vault is notorious for this) have their own access policies that operate independently of RBAC. If you're using Key Vault and getting 403 despite correct RBAC, make sure your Key Vault is using the Azure role-based access control permission model (under Settings → Access configuration) rather than the legacy Vault access policies model. The two models don't mix, if you assign RBAC roles but the vault uses access policies, the RBAC grants have no effect.
Enterprise/Domain-Joined Scenarios
In corporate environments with strict Azure Policy assignments, the creation of managed identities may be blocked by policy. Check the Azure Policy compliance blade for your subscription. Look for policies in the "Deny" effect related to identity or service principal creation. You may need your Azure administrator to grant an exemption or adjust the policy scope before you can create user-assigned identities in certain resource groups.
Prevention & Best Practices for Azure Managed Identity
Once Azure managed identity is working, here's how to keep it working and avoid the same issues in future deployments. Most of these come from lessons learned the hard way.
Use user-assigned identities for anything that gets redeployed. If your infrastructure-as-code pipeline destroys and recreates VMs, containers, or App Service instances as part of deployments, system-assigned identities will cause you grief, they get deleted and recreated with the resource, wiping out all your role assignments. User-assigned identities survive resource recreation. Attach the same identity to the new resource and all your RBAC grants stay intact.
Name your managed identities clearly and consistently. I've seen environments with a dozen managed identities named mi-1, mi-dev, identity-test, and nobody can tell which one goes with what. Use a convention like {app}-{env}-identity (e.g., invoicing-prod-identity) and tag every managed identity with the owning team, application, and cost center.
Audit role assignments quarterly. Use the Azure Policy built-in initiative for identity governance, or run this Azure CLI command to export all role assignments for your subscription and pipe it into a spreadsheet review:
az role assignment list --all --output table --query "[].{Principal:principalName, Role:roleDefinitionName, Scope:scope}"
Look for orphaned assignments (principals that no longer exist), overly broad scopes (assignments at subscription level when resource level would do), and over-privileged roles (Contributor when Reader would suffice).
Avoid storing Client IDs in code. Put the managed identity Client ID in an environment variable or application configuration setting, not hardcoded in source. This makes it easy to swap identities across environments without a code change.
- Always prefer user-assigned managed identities over system-assigned for workloads where the compute resource might be recreated
- Set Azure Policy to deny the creation of storage account keys and SAS tokens in production, forces teams to use managed identity authentication
- Add managed identity sign-in activity to your security monitoring alerts, unexpected token acquisition patterns can signal credential abuse
- Document the managed identity Client ID and its role assignments in your runbook, the next on-call engineer will thank you
Frequently Asked Questions
What is Azure Managed Identity and why should I use it instead of a service principal with a secret?
Azure managed identity is a feature that gives your Azure compute resources (VMs, App Services, AKS nodes, and more) an automatically managed identity in Microsoft Entra ID. Instead of generating a client secret or certificate and storing it somewhere in your code or config, the Azure platform handles credential rotation and token issuance entirely on your behalf. You don't have secrets to rotate, accidentally commit to Git, or lose track of in a key vault. The practical benefit is that your application code can call Azure Storage, Key Vault, Service Bus, and other Entra-aware services without any credentials ever appearing in your codebase, environment variables, or configuration files. Microsoft also offers managed identities at no extra cost, you're not paying a premium for the security improvement.
What's the actual difference between system-assigned and user-assigned managed identity?
The key difference is lifecycle and sharing. A system-assigned managed identity lives and dies with the Azure resource it's attached to, delete the VM, the identity is gone, and so are all its role assignments. A user-assigned managed identity is a standalone Azure resource that you create separately and can attach to multiple VMs, App Services, or other compute resources simultaneously. If your VM gets recreated during a deployment, a user-assigned identity survives that recreation and retains all its RBAC permissions. Microsoft recommends user-assigned managed identities for most production scenarios, especially where resources are recycled frequently or where you need a single identity to span multiple resources, for example, ten VMs all needing read access to the same Storage Account.
My managed identity keeps getting 401 errors even though I assigned an RBAC role. What am I missing?
The most common cause is that you assigned the role to the wrong resource, check that the role assignment is on the target resource (the Storage Account, Key Vault, etc.), not on the compute resource (the VM or App Service). Also check that the role assignment has had 1–2 minutes to propagate before testing. If you're using Key Vault specifically, verify under Settings → Access configuration that the vault is using the "Azure role-based access control" permission model and not the older "Vault access policies" model, RBAC role assignments have no effect on a vault that still uses access policies. Finally, confirm in Microsoft Entra ID sign-in logs that the token acquisition itself is succeeding; a 401 from the application could mean the token was acquired but the role hasn't propagated yet, or it could mean token acquisition is failing entirely.
Can I use Azure managed identity to authenticate between two of my own Azure services?
Yes, and this is one of the most underused capabilities. Your application running on a VM or App Service can use its managed identity to call any service that supports Microsoft Entra authentication, including your own APIs hosted on other App Services or Azure Functions. On the calling side, you use the Azure.Identity SDK to acquire a token scoped to the target application's app registration client ID. On the receiving side, your API validates the incoming bearer token using standard Entra middleware. This eliminates shared secrets between your own microservices entirely. The token audience in this case is your target application's URI or client ID, not a standard Azure service endpoint.
How do I find the Client ID of a user-assigned managed identity?
Open the Azure Portal and navigate to Managed Identities (search for it in the top bar). Click on your specific identity. On the Overview blade you'll see both the Client ID (a GUID that uniquely identifies the identity in Entra ID) and the Object (principal) ID. The Client ID is what you pass to the ManagedIdentityCredential constructor in Azure.Identity SDK. The Object ID is what Azure RBAC uses internally when you add the identity to a role assignment, the portal typically looks up by name, but the Object ID is what gets stored. You can also retrieve the Client ID via Azure CLI with: az identity show --name YOUR_IDENTITY_NAME --resource-group YOUR_RG --query clientId -o tsv
Does Azure managed identity work in Azure Kubernetes Service (AKS)?
Yes, but the setup is more involved than with VMs or App Services. AKS supports two approaches: node-level managed identity (where all pods on a node share the same identity, simpler but less granular) and AKS Workload Identity (the recommended modern approach), which federates per-pod Kubernetes service accounts with user-assigned managed identities. With Workload Identity, each pod can have its own Azure managed identity with its own scoped RBAC assignments, giving you proper least-privilege separation between microservices. The Azure.Identity SDK detects the workload identity token file automatically when the correct environment variables are injected by the Workload Identity webhook, no code changes are needed compared to a VM-based deployment.