System-Assigned Managed Identities
What is a System-Assigned Managed Identity?
A system-assigned managed identity is an Azure Active Directory identity that is automatically created and tied to the lifecycle of a specific Azure resource (like a VM, App Service, or Function App). It eliminates the need to manage credentials in your code.
Key Characteristics:
- Lifecycle Tied to Resource: Created when you enable it, deleted when you delete the resource
- One-to-One Relationship: Each resource can have only ONE system-assigned identity
- Automatic Management: Azure handles creation, rotation, and deletion
- No Credentials to Manage: No passwords, certificates, or secrets to store
- Azure AD Service Principal: Creates a special service principal in your tenant
System-Assigned vs User-Assigned
| Feature | System-Assigned | User-Assigned |
|---|---|---|
| Lifecycle | Tied to resource | Independent resource |
| Sharing | Cannot share across resources | Can assign to multiple resources |
| Use Case | Single resource needs identity | Multiple resources need same identity |
| Management | Auto-created/deleted | Manual lifecycle management |
| When to Use | Most common scenarios | Shared identity scenarios |
How to Use System-Assigned Managed Identity
Enable on Azure VM
Using Azure CLI
# Enable system-assigned identity on existing VM
az vm identity assign \
--resource-group rg-compute \
--name vm-app-01
# Enable during VM creation
az vm create \
--resource-group rg-compute \
--name vm-app-02 \
--image UbuntuLTS \
--assign-identity \
--admin-username azureuser \
--generate-ssh-keys
# Get the principal ID (for role assignments)
PRINCIPAL_ID=$(az vm identity show \
--resource-group rg-compute \
--name vm-app-01 \
--query principalId \
--output tsv)
echo $PRINCIPAL_ID
Enable on App Service / Function App
# Enable system-assigned identity
az webapp identity assign \
--resource-group rg-app \
--name app-mywebapp-prod
# Get principal ID
az webapp identity show \
--resource-group rg-app \
--name app-mywebapp-prod \
--query principalId -o tsv
Enable on Azure Container Instance
az container create \
--resource-group rg-containers \
--name mycontainer \
--image mcr.microsoft.com/azuredocs/aci-helloworld \
--assign-identity \
--location eastus
Grant Permissions to Managed Identity
Once enabled, grant the identity access to Azure resources using RBAC:
# Grant Storage Blob Data Contributor role to VM's identity
az role assignment create \
--role \"Storage Blob Data Contributor\" \
--assignee $PRINCIPAL_ID \
--scope /subscriptions/{subscription-id}/resourceGroups/rg-storage/providers/Microsoft.Storage/storageAccounts/mystorageaccount
# Grant Key Vault Secrets User role
az role assignment create \
--role \"Key Vault Secrets User\" \
--assignee $PRINCIPAL_ID \
--scope /subscriptions/{subscription-id}/resourceGroups/rg-security/providers/Microsoft.KeyVault/vaults/kv-app-prod
# Grant SQL DB Contributor role
az role assignment create \
--role \"SQL DB Contributor\" \
--assignee $PRINCIPAL_ID \
--scope /subscriptions/{subscription-id}/resourceGroups/rg-data/providers/Microsoft.Sql/servers/sql-prod-server
Terraform Example
# Virtual Machine with System-Assigned Identity
resource \"azurerm_linux_virtual_machine\" \"app\" {\n name = \"vm-app-prod-01\"
resource_group_name = azurerm_resource_group.compute.name
location = azurerm_resource_group.compute.location
size = \"Standard_D2s_v3\"
# Enable system-assigned managed identity
identity {
type = \"SystemAssigned\"
}
# ... other VM configuration ...
admin_username = \"azureuser\"
admin_ssh_key {
username = \"azureuser\"
public_key = file(\"~/.ssh/id_rsa.pub\")
}
os_disk {
caching = \"ReadWrite\"
storage_account_type = \"Premium_LRS\"
}
source_image_reference {
publisher = \"Canonical\"
offer = \"0001-com-ubuntu-server-jammy\"
sku = \"22_04-lts-gen2\"
version = \"latest\"
}
}
# Grant access to Key Vault
resource \"azurerm_role_assignment\" \"vm_keyvault\" {\n scope = azurerm_key_vault.app.id
role_definition_name = \"Key Vault Secrets User\"
principal_id = azurerm_linux_virtual_machine.app.identity[0].principal_id
}
# Grant access to Storage Account
resource \"azurerm_role_assignment\" \"vm_storage\" {\n scope = azurerm_storage_account.app.id
role_definition_name = \"Storage Blob Data Contributor\"
principal_id = azurerm_linux_virtual_machine.app.identity[0].principal_id
}
# App Service with System-Assigned Identity
resource \"azurerm_linux_web_app\" \"api\" {\n name = \"app-api-prod\"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
service_plan_id = azurerm_service_plan.app.id
# Enable system-assigned identity
identity {
type = \"SystemAssigned\"
}
site_config {
always_on = true
}
app_settings = {
\"WEBSITE_RUN_FROM_PACKAGE\" = \"1\"
}
}
# Grant App Service access to SQL Database
resource \"azurerm_role_assignment\" \"app_sql\" {\n scope = azurerm_mssql_server.main.id
role_definition_name = \"SQL DB Contributor\"
principal_id = azurerm_linux_web_app.api.identity[0].principal_id
}
# Azure Function with System-Assigned Identity
resource \"azurerm_linux_function_app\" \"processor\" {\n name = \"func-processor-prod\"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
service_plan_id = azurerm_service_plan.functions.id
storage_account_name = azurerm_storage_account.functions.name
storage_account_access_key = azurerm_storage_account.functions.primary_access_key
identity {
type = \"SystemAssigned\"
}
site_config {
application_stack {
python_version = \"3.11\"
}
}
}
Using Managed Identity in Application Code
.NET Example (Accessing Key Vault)
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
// DefaultAzureCredential automatically uses managed identity when running in Azure
var client = new SecretClient(
new Uri(\"https://kv-app-prod.vault.azure.net/\"),
new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync(\"db-connection-string\");
string connectionString = secret.Value;
Python Example (Accessing Storage)
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
# DefaultAzureCredential uses managed identity automatically
credential = DefaultAzureCredential()
blob_service_client = BlobServiceClient(
account_url=\"https://mystorageaccount.blob.core.windows.net\",
credential=credential
)
# Upload blob
blob_client = blob_service_client.get_blob_client(
container=\"mycontainer\",
blob=\"myfile.txt\"
)
blob_client.upload_blob(\"Hello, World!\")
Node.js Example (Accessing Cosmos DB)
const { DefaultAzureCredential } = require(\"@azure/identity\");
const { CosmosClient } = require(\"@azure/cosmos\");
const credential = new DefaultAzureCredential();
const endpoint = \"https://mycosmosdb.documents.azure.com:443/\";
const client = new CosmosClient({ endpoint, aadCredentials: credential });
const database = client.database(\"mydb\");
const container = database.container(\"items\");
// Query data
const { resources } = await container.items.query(\"SELECT * FROM c\").fetchAll();
CI/CD Integration
GitHub Actions with Managed Identity
When self-hosted runners use managed identity:
name: Deploy Application
on: [push]
jobs:
deploy:
runs-on: [self-hosted, linux] # Self-hosted runner with managed identity
steps:
- uses: actions/checkout@v2
- name: Azure Login (uses managed identity automatically)
uses: azure/login@v1
with:
# Leave creds empty - uses managed identity of runner VM
enable-AzPSSession: true
- name: Deploy to App Service
run: |\n az webapp deployment source config-zip \\\n --resource-group rg-app \\\n --name app-api-prod \\\n --src app.zip
Azure DevOps with Managed Identity
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: AzureCLI@2
displayName: 'Deploy using Managed Identity'
inputs:
azureSubscription: 'ManagedIdentityConnection' # Service connection using managed identity
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |\n # Access Key Vault
SECRET=$(az keyvault secret show \\\n --vault-name kv-app-prod \\\n --name api-key \\\n --query value -o tsv)
# Deploy application
az webapp config appsettings set \\\n --resource-group rg-app \\\n --name app-api-prod \\\n --settings API_KEY=$SECRET
Best Practices
1. Prefer System-Assigned for Single-Resource Scenarios
✅ Use system-assigned when:
- Identity needed for only one resource
- Want automatic lifecycle management
- Don't need to share identity across resources
❌ Don't use system-assigned when:
- Multiple resources need same identity
- Identity must persist after resource deletion
- Rapid resource creation/deletion (rate limits)
2. Use DefaultAzureCredential in Code
✅ Do:
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential()
This automatically uses:
- Managed Identity (when in Azure)
- Azure CLI (when developing locally)
- Visual Studio Code (when debugging)
- Environment variables (in other scenarios)
❌ Don't hardcode credentials or use connection strings with passwords
3. Grant Least Privilege Access
✅ Specific roles:
az role assignment create \\\n --role \"Storage Blob Data Reader\" \\\n --assignee $PRINCIPAL_ID \\\n --scope /subscriptions/{sub}/resourceGroups/rg-storage/providers/Microsoft.Storage/storageAccounts/mystorageaccount/blobServices/default/containers/mycontainer
❌ Overly broad access:
# Too broad - don't do this
az role assignment create \\\n --role \"Contributor\" \\\n --assignee $PRINCIPAL_ID \\\n --scope /subscriptions/{subscription-id}
4. Use for Azure-to-Azure Authentication Only
✅ Use managed identity for:
- VM accessing Key Vault
- App Service accessing Storage
- Function accessing Cosmos DB
- AKS accessing Container Registry
❌ Don't use for:
- External SaaS API authentication
- On-premises resource access
- Third-party services outside Azure
5. Enable Diagnostic Logging
# Enable sign-in logs for managed identity
# View in Azure AD > Sign-in logs > Service principal sign-ins
Common Use Cases
1. App Service Accessing SQL Database
// No connection string needed!
using Microsoft.Data.SqlClient;
using Azure.Identity;
var connectionString = \"Server=sql-prod-server.database.windows.net;Database=mydb;\";
using var connection = new SqlConnection(connectionString);
connection.AccessToken = await new DefaultAzureCredential()
.GetTokenAsync(new TokenRequestContext(new[] { \"https://database.windows.net/.default\" }));
connection.Open();
2. VM Accessing Key Vault
# Inside VM with system-assigned identity enabled
# Get access token
TOKEN=$(curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net' -H Metadata:true | jq -r .access_token)
# Access Key Vault secret
curl https://kv-app-prod.vault.azure.net/secrets/db-password?api-version=7.4 \\\n -H \"Authorization: Bearer $TOKEN\"
3. Function App Writing to Storage
import azure.functions as func
from azure.storage.blob import BlobServiceClient
from azure.identity import DefaultAzureCredential
def main(req: func.HttpRequest) -> func.HttpResponse:
# Managed identity used automatically
blob_service = BlobServiceClient(
account_url=\"https://mystorageaccount.blob.core.windows.net\",
credential=DefaultAzureCredential()
)
container_client = blob_service.get_container_client(\"uploads\")
blob_client = container_client.get_blob_client(\"data.txt\")
blob_client.upload_blob(req.get_body(), overwrite=True)
return func.HttpResponse(\"Success\")
Things to Avoid
❌ Don't disable managed identity and use connection strings with passwords ❌ Don't grant Owner or Contributor roles to managed identities ❌ Don't use managed identity for on-premises resource access ❌ Don't create/delete resources rapidly (hits rate limits on service principal creation) ❌ Don't share credentials between environments (dev/prod) ❌ Don't use for user authentication (use Azure AD B2C/B2B instead) ❌ Don't forget to grant RBAC permissions after enabling identity ❌ Don't rely on managed identity for external OAuth flows
✅ Do use managed identity for all Azure-to-Azure authentication ✅ Do use DefaultAzureCredential in application code ✅ Do grant least privilege RBAC roles ✅ Do enable diagnostic logging ✅ Do test managed identity locally using Azure CLI credentials ✅ Do use user-assigned identity when sharing across resources ✅ Do document which resources use which identities ✅ Do regularly audit role assignments
Troubleshooting
Check if Identity is Enabled
# VM
az vm identity show --resource-group rg-compute --name vm-app-01
# App Service
az webapp identity show --resource-group rg-app --name app-api-prod
# Check role assignments
az role assignment list --assignee $PRINCIPAL_ID -o table
Test Managed Identity from VM
# SSH into VM
# Get access token
curl 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -H Metadata:true
# If this fails:
# 1. Check identity is enabled
# 2. Verify RBAC role assignments
# 3. Check firewall rules on target resource
Common Errors
Error: "ManagedIdentityCredential authentication failed"
- Cause: Managed identity not enabled on resource
- Fix: Enable identity using
az <resource> identity assign
Error: "Forbidden" or 403
- Cause: Missing RBAC role assignment
- Fix: Grant appropriate role to managed identity
Error: "No MSI Found"
- Cause: Trying to use managed identity outside Azure
- Fix: Use Azure CLI or service principal for local development
Security Considerations
- No Secrets to Rotate: Managed identity credentials are automatically rotated
- Audit Trail: All actions logged in Azure AD sign-in logs
- Conditional Access: Can apply CA policies to service principals
- Least Privilege: Always grant minimum required permissions
- Scope Restrictions: Assign roles at resource/RG level, not subscription