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.

FactorUse system-assignedUse user-assigned
Number of resources needing this identityOne resource onlyTwo or more resources need identical access
Resource replacement frequencyResource is long-lived and rarely replacedResources are replaced regularly (blue/green deployments, scaling)
RBAC assignment managementSimple — one assignment per resourceEfficient — one assignment covers all attached resources
Identity pre-provisioningNot possible — identity is created with the resourceIdentity exists before the resource; RBAC can be set up in advance
Audit trail clarityClean — identity name matches resource nameRequires good naming conventions so it is clear what the identity is for
Terraform / Bicep complexitySimpler — enabled with a single flagRequires a separate resource block for the identity
PortabilityCannot be moved to another resourceCan 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:

  1. Enable system-assigned identity on all 10 functions.
  2. Retrieve the principal ID from each function individually (10 separate az functionapp identity show calls).
  3. Create 10 separate RBAC role assignments on the Key Vault — one for each principal ID.
  4. 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.
  5. 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 json

The 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"
done

When 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-rg

User-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-rg

Terraform 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

  1. 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.
  2. 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.
  3. 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 show and detach from all resources before deleting the identity.
  4. 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.

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.

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