System-Assigned vs User-Assigned Managed Identities in Azure
Azure managed identities come in two forms: system-assigned, where the identity is born and dies with the resource, and user-assigned, where the identity is a standalone object you manage independently. Choosing the right type affects how you write Terraform, how many RBAC assignments you need to maintain, and what happens when resources are replaced.
The core difference: lifecycle
The single most important distinction between the two identity types is lifecycle ownership.
A system-assigned identity cannot exist without its parent resource. When you create a VM with a system-assigned identity, Azure creates a corresponding service principal in Microsoft Entra ID linked to that VM. When you delete the VM, Azure deletes the service principal automatically. There is no orphaned identity to clean up. You also cannot manually delete the identity while the VM exists — the two are fused.
A user-assigned identity is its own Azure resource with its own resource group, name, and lifecycle. You create it, attach it to resources, detach it from resources, and delete it entirely as separate operations. Deleting a resource that uses a user-assigned identity does not delete the identity. Deleting the identity without removing it from attached resources will break authentication on those resources until the attachment is updated.
Decision table: when to use each type
Use this table as a starting point. The scenario section below provides a real-world example that makes the user-assigned choice concrete.
| Factor | Use system-assigned | Use user-assigned |
|---|---|---|
| Number of resources needing this identity | One resource only | Two or more resources need identical access |
| Resource replacement frequency | Resource is long-lived and rarely replaced | Resources are replaced regularly (blue/green deployments, scaling) |
| RBAC assignment management | Simple — one assignment per resource | Efficient — one assignment covers all attached resources |
| Identity pre-provisioning | Not possible — identity is created with the resource | Identity exists before the resource; RBAC can be set up in advance |
| Audit trail clarity | Clean — identity name matches resource name | Requires good naming conventions so it is clear what the identity is for |
| Terraform / Bicep complexity | Simpler — enabled with a single flag | Requires a separate resource block for the identity |
| Portability | Cannot be moved to another resource | Can be detached from one resource and attached to another |
Real scenario: 10 Azure Functions, one shared identity
A product team runs a microservices application on Azure Functions. All 10 functions need to read configuration secrets from the same Azure Key Vault. The team is evaluating which identity type to use.
Option A: system-assigned identity on each function (the painful path)
Each Azure Function gets its own system-assigned identity. The team must:
- Enable system-assigned identity on all 10 functions.
- Retrieve the principal ID from each function individually (10 separate
az functionapp identity showcalls). - Create 10 separate RBAC role assignments on the Key Vault — one for each principal ID.
- When a function is replaced (for example, during a slot swap or a Terraform destroy/create), the old system-assigned identity is deleted and a new one is created. The RBAC assignment for the old identity becomes orphaned, and a new assignment must be created for the new identity.
- Over six months, as functions are redeployed, the team accumulates orphaned role assignments that clutter the access control list.
Option B: one user-assigned identity shared across all 10 functions (the clean path)
The team creates a single user-assigned identity named func-keyvault-reader:
# Create the user-assigned identity once
az identity create \
--name func-keyvault-reader \
--resource-group my-rg \
--location eastus
# Get its principal ID and client ID
az identity show \
--name func-keyvault-reader \
--resource-group my-rg \
--query '{principalId:principalId, clientId:clientId}' \
--output jsonThe team assigns the Key Vault Secrets User role to this identity exactly once:
PRINCIPAL_ID=$(az identity show \
--name func-keyvault-reader \
--resource-group my-rg \
--query principalId \
--output tsv)
VAULT_ID=$(az keyvault show \
--name my-config-vault \
--resource-group my-rg \
--query id \
--output tsv)
az role assignment create \
--assignee "$PRINCIPAL_ID" \
--role "Key Vault Secrets User" \
--scope "$VAULT_ID"Then they attach the identity to each function. This can be scripted:
IDENTITY_ID=$(az identity show \
--name func-keyvault-reader \
--resource-group my-rg \
--query id \
--output tsv)
for FUNC in func-orders func-payments func-inventory func-notifications func-reports \
func-audit func-alerts func-sync func-export func-cleanup; do
az functionapp identity assign \
--name "$FUNC" \
--resource-group my-rg \
--identities "$IDENTITY_ID"
doneWhen any function is replaced during a deployment, the new function instance is assigned the same identity resource. The RBAC assignment is on the identity, not on the function, so it stays valid. The team maintains exactly one role assignment instead of ten, and no orphaned assignments accumulate over time.
When code uses a user-assigned identity, pass the client ID explicitly to avoid ambiguity if the resource also has a system-assigned identity: ManagedIdentityCredential(client_id=“<user-assigned-client-id>”). If you omit it and only a system-assigned identity exists, the SDK picks it up automatically.
Creating and assigning identities via the CLI
System-assigned: VM example
# Enable system-assigned identity at VM creation
az vm create \
--name my-vm \
--resource-group my-rg \
--image Ubuntu2204 \
--assign-identity \
--size Standard_B2s
# Or enable it on an existing VM
az vm identity assign \
--name my-vm \
--resource-group my-rg
# View the identity
az vm identity show \
--name my-vm \
--resource-group my-rgUser-assigned: create, then attach
# Step 1: Create the identity as a standalone resource
az identity create \
--name my-shared-identity \
--resource-group my-rg \
--location eastus
# Step 2: Get the full resource ID
IDENTITY_ID=$(az identity show \
--name my-shared-identity \
--resource-group my-rg \
--query id \
--output tsv)
# Step 3: Attach to a VM
az vm identity assign \
--name my-vm \
--resource-group my-rg \
--identities "$IDENTITY_ID"
# Step 4: Attach to an App Service
az webapp identity assign \
--name my-app \
--resource-group my-rg \
--identities "$IDENTITY_ID"Removing a user-assigned identity from a resource
# Detach from a specific VM (does not delete the identity resource)
az vm identity remove \
--name my-vm \
--resource-group my-rg \
--identities "$IDENTITY_ID"
# To fully delete the identity resource itself
az identity delete \
--name my-shared-identity \
--resource-group my-rgTerraform patterns for each identity type
In Terraform, system-assigned identities are enabled inline on the resource block. User-assigned identities require a separate resource and a reference.
# System-assigned: inline on the resource
resource "azurerm_linux_virtual_machine" "example" {
name = "my-vm"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
size = "Standard_B2s"
# ... other config ...
identity {
type = "SystemAssigned"
}
}
# Access the principal ID for RBAC assignment
output "vm_principal_id" {
value = azurerm_linux_virtual_machine.example.identity[0].principal_id
}# User-assigned: separate resource, then reference
resource "azurerm_user_assigned_identity" "shared" {
name = "func-keyvault-reader"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
}
resource "azurerm_linux_function_app" "example" {
name = "my-function"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
# ... other config ...
identity {
type = "UserAssigned"
identity_ids = [azurerm_user_assigned_identity.shared.id]
}
}
resource "azurerm_role_assignment" "kv_reader" {
scope = azurerm_key_vault.example.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_user_assigned_identity.shared.principal_id
}When neither type works
Managed identities require Azure-hosted compute. If your workload runs on-premises, in GitHub Actions, or on another cloud provider, you cannot use a managed identity. In those cases you need a service principal with a credential — but you should still minimize the credential’s scope and rotation burden. GitHub Actions supports OIDC-based federation with Entra ID, which provides a similar passwordless experience without requiring Azure-hosted compute. See Azure service principals explained for the broader service principal landscape.
Common mistakes when choosing identity types
- Using system-assigned for fleet deployments. If your deployment pipeline regularly destroys and recreates resources (common with immutable infrastructure patterns), system-assigned identities create orphaned RBAC assignments every cycle. Use user-assigned identities for any resource that is replaced rather than updated.
- Creating one user-assigned identity per resource anyway. If you create a separate user-assigned identity for each resource, you lose the main benefit of the type. User-assigned identities earn their complexity only when shared across multiple resources or pre-provisioned before the resource exists.
- Deleting a user-assigned identity that is still attached to resources. The deletion will succeed, but the attached resources will fail to authenticate at runtime. Always check attachments with
az identity showand detach from all resources before deleting the identity. - Not passing the client ID in code when multiple identities are attached. If a resource has both a system-assigned identity and a user-assigned identity, calling
DefaultAzureCredential()with no arguments may pick the wrong one. Pass the client ID of the identity you intend to use.
Summary
- System-assigned identities are fused to a single resource and deleted when that resource is deleted — simple for single-resource scenarios.
- User-assigned identities are standalone resources that can be attached to multiple resources and survive resource deletion.
- Use user-assigned identities when multiple resources need the same permissions, when resources are regularly replaced, or when you need to pre-provision access before the resource exists.
- A single user-assigned identity eliminates the need for N separate RBAC assignments when N resources need the same access.
- In code, pass the client ID explicitly when using a user-assigned identity to avoid ambiguity.
- In Terraform, user-assigned identities require a separate
azurerm_user_assigned_identityresource block.
Frequently asked questions
Can a resource have both a system-assigned and a user-assigned identity at the same time?
Yes. A resource can have one system-assigned identity and one or more user-assigned identities simultaneously. The code must specify which identity to use by passing the client ID of the user-assigned identity, or omit it to use the system-assigned identity. Most teams avoid mixing both types on the same resource to keep the configuration clear.
What happens to a user-assigned identity when I delete all the resources attached to it?
Nothing — a user-assigned identity persists independently of the resources it is attached to. You must delete the user-assigned identity resource itself explicitly. This is intentional, because the identity may be detached and re-attached to replacement resources during deployments.
Is there a limit on how many resources can share a single user-assigned identity?
A user-assigned identity can be attached to up to 1000 Azure resources. A single VM or App Service can have up to 20 user-assigned identities attached at one time. These limits are high enough that they rarely matter in practice.