Skip to main content

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

FeatureSystem-AssignedUser-Assigned
LifecycleTied to resourceIndependent resource
SharingCannot share across resourcesCan assign to multiple resources
Use CaseSingle resource needs identityMultiple resources need same identity
ManagementAuto-created/deletedManual lifecycle management
When to UseMost common scenariosShared 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:

  1. Managed Identity (when in Azure)
  2. Azure CLI (when developing locally)
  3. Visual Studio Code (when debugging)
  4. 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

  1. No Secrets to Rotate: Managed identity credentials are automatically rotated
  2. Audit Trail: All actions logged in Azure AD sign-in logs
  3. Conditional Access: Can apply CA policies to service principals
  4. Least Privilege: Always grant minimum required permissions
  5. Scope Restrictions: Assign roles at resource/RG level, not subscription