Azure Managed Identity: Passwordless Authentication for Azure Services

Azure Managed Identity eliminates credentials from your code by providing an automatically managed identity for Azure resources. Instead of storing connection strings and passwords, your application r

Introduction#

Azure Managed Identity eliminates credentials from your code by providing an automatically managed identity for Azure resources. Instead of storing connection strings and passwords, your application requests a token from the Azure Instance Metadata Service and uses it to authenticate to Azure services. No passwords to rotate, no secrets to store, no risk of credentials leaking into logs.

System vs User-Assigned Identities#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
System-Assigned Managed Identity:
  - Created and deleted with the resource (VM, App Service, Function)
  - One-to-one: each resource has its own identity
  - Use when: the identity belongs to one specific resource

User-Assigned Managed Identity:
  - Created independently, assigned to multiple resources
  - Persists when resources are deleted
  - Use when: multiple resources share the same permissions
              or you need identity before resource creation

Examples:
  System-assigned: "this API pod's identity" — deleted when pod is deleted
  User-assigned: "billing-service-identity" — shared by all billing pods,
                 survives pod restarts and redeploys

Terraform: Create and Assign Managed Identity#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# User-assigned managed identity
resource "azurerm_user_assigned_identity" "api_service" {
  name                = "api-service-identity"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
}

# Assign to an App Service
resource "azurerm_linux_web_app" "api" {
  name                = "my-api-service"
  resource_group_name = azurerm_resource_group.main.name
  location            = azurerm_resource_group.main.location
  service_plan_id     = azurerm_service_plan.main.id

  identity {
    type         = "UserAssigned"
    identity_ids = [azurerm_user_assigned_identity.api_service.id]
  }
}

# Grant the identity access to Key Vault secrets
resource "azurerm_key_vault_access_policy" "api_service" {
  key_vault_id = azurerm_key_vault.main.id
  tenant_id    = data.azurerm_client_config.current.tenant_id
  object_id    = azurerm_user_assigned_identity.api_service.principal_id

  secret_permissions = ["Get", "List"]
}

# Grant the identity read access to a Storage Account
resource "azurerm_role_assignment" "api_storage_reader" {
  scope                = azurerm_storage_account.main.id
  role_definition_name = "Storage Blob Data Reader"
  principal_id         = azurerm_user_assigned_identity.api_service.principal_id
}

# Grant access to Azure SQL Database
resource "azurerm_mssql_server_active_directory_administrator" "main" {
  server_id   = azurerm_mssql_server.main.id
  login       = "api-service-identity"
  object_id   = azurerm_user_assigned_identity.api_service.principal_id
  tenant_id   = data.azurerm_client_config.current.tenant_id
}

Python: Authenticate Using Managed Identity#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from azure.identity import DefaultAzureCredential, ManagedIdentityCredential
from azure.keyvault.secrets import SecretClient
from azure.storage.blob import BlobServiceClient

# DefaultAzureCredential: tries multiple auth methods in order:
# 1. Environment variables (AZURE_CLIENT_ID, etc.)
# 2. Workload identity (Kubernetes)
# 3. Managed identity
# 4. Visual Studio Code credentials (local dev)
# 5. Azure CLI credentials (local dev)
# This single credential works in both local dev and production

credential = DefaultAzureCredential()

# Key Vault: read secrets without storing any credentials
secret_client = SecretClient(
    vault_url="https://my-keyvault.vault.azure.net/",
    credential=credential,
)

db_password = secret_client.get_secret("database-password").value
api_key = secret_client.get_secret("third-party-api-key").value

# Azure Blob Storage: access without connection strings
blob_client = BlobServiceClient(
    account_url="https://mystorageaccount.blob.core.windows.net",
    credential=credential,
)

container_client = blob_client.get_container_client("uploads")

# Upload a file — no credentials in code
with open("report.pdf", "rb") as data:
    container_client.upload_blob("reports/2025-Q4.pdf", data, overwrite=True)

# For user-assigned identity (specify client_id)
specific_credential = ManagedIdentityCredential(
    client_id="<user-assigned-identity-client-id>"
)

Azure SQL with Managed Identity (Python)#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import struct
import pyodbc
from azure.identity import DefaultAzureCredential

def get_sql_connection(server: str, database: str):
    """Connect to Azure SQL using managed identity — no password."""
    credential = DefaultAzureCredential()

    # Get an access token for Azure SQL
    token = credential.get_token("https://database.windows.net/.default")
    token_bytes = token.token.encode("utf-16-le")
    token_struct = struct.pack(f"<I{len(token_bytes)}s", len(token_bytes), token_bytes)

    conn_str = (
        f"Driver={{ODBC Driver 18 for SQL Server}};"
        f"Server={server};"
        f"Database={database};"
        f"Encrypt=yes;"
        f"TrustServerCertificate=no;"
    )

    conn = pyodbc.connect(conn_str, attrs_before={1256: token_struct})
    return conn

# In Azure SQL: grant the managed identity database access
# CREATE USER [api-service-identity] FROM EXTERNAL PROVIDER;
# ALTER ROLE db_datareader ADD MEMBER [api-service-identity];
# ALTER ROLE db_datawriter ADD MEMBER [api-service-identity];

Kubernetes: Workload Identity#

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# AKS Workload Identity: bind Kubernetes service account to Azure managed identity
# No secrets stored in Kubernetes

# 1. Create federated credential (Terraform)
# resource "azurerm_federated_identity_credential" "api" {
#   name      = "api-service-fed-credential"
#   resource_group_name = ...
#   parent_id = azurerm_user_assigned_identity.api_service.id
#   audience  = ["api://AzureADTokenExchange"]
#   issuer    = data.azurerm_kubernetes_cluster.main.oidc_issuer_url
#   subject   = "system:serviceaccount:production:api-service"
# }

# 2. Kubernetes ServiceAccount with Azure annotations
apiVersion: v1
kind: ServiceAccount
metadata:
  name: api-service
  namespace: production
  annotations:
    azure.workload.identity/client-id: "<user-assigned-identity-client-id>"
---
# 3. Pod using the service account — gets Azure identity automatically
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
  namespace: production
spec:
  template:
    metadata:
      labels:
        azure.workload.identity/use: "true"  # enables token injection
    spec:
      serviceAccountName: api-service
      containers:
      - name: api
        image: myregistry.azurecr.io/api:latest
        env:
        - name: AZURE_CLIENT_ID
          value: "<user-assigned-identity-client-id>"
        # Azure SDK automatically uses the injected token
        # DefaultAzureCredential → WorkloadIdentityCredential → success

Local Development with Azure CLI#

1
2
3
4
5
6
7
8
9
10
11
12
# For local development, Azure CLI credentials work with DefaultAzureCredential
az login

# Assign yourself the same roles as the managed identity for testing
az role assignment create \
    --role "Key Vault Secrets User" \
    --assignee "your-email@company.com" \
    --scope "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>"

# Your local DefaultAzureCredential will use az CLI credentials
# Production DefaultAzureCredential will use managed identity
# Same code, different identity source

Conclusion#

Azure Managed Identity removes the credential management burden from application teams. Secrets stop being a deployment artifact — there is nothing to rotate, nothing to accidentally commit to git, and no blast radius from a leaked connection string. DefaultAzureCredential from the Azure SDK handles the auth flow automatically in any environment. For Kubernetes, Workload Identity federates the pod’s service account with Azure AD without injecting secrets into the cluster. Combine with Azure Key Vault for secrets that cannot be replaced by managed identity (third-party API keys, certificates) and you have a complete passwordless approach to Azure service authentication.

Contents