Stop Using Connection Strings:
A Step-by-Step Guide to Azure Managed Identities in 2026
Every hardcoded connection string in your codebase is a liability waiting to become an incident. Azure Managed Identities eliminate credentials entirely — no passwords, no rotation schedules, no leaked secrets in Git history. This guide walks through every scenario: App Service, Azure Functions, AKS, Azure SQL, Key Vault, Blob Storage, and Service Bus — with working code in Python, C#, and Bicep.
Why Connection Strings Are a Security Liability You Cannot Afford
A connection string with an embedded password is a credential that exists in at least five places you did not intend: your application config file, your deployment pipeline variables, your team's local environment files, your Git history (if a developer ever committed it by mistake), and the memory of every developer who has ever set up the application. Each of those locations is a potential exfiltration point.
The problem compounds as the credential ages. Rotation schedules are missed. New team members are onboarded with the old credentials. Old credentials accumulate in old CI/CD variable sets that nobody audits. The connection string that was "temporary" two years ago is now production-critical and nobody knows when it was last changed or who has access to it.
Azure Managed Identity eliminates all of this. It gives your Azure resource — a VM, an App Service, an Azure Function, an AKS pod — its own identity in Microsoft Entra ID. That identity can be granted precise RBAC permissions on target resources. When your code needs to access Azure SQL, Key Vault, or Blob Storage, it requests a short-lived access token using that identity. No password, no connection string, no rotation — Azure handles all of it automatically.
The security outcome is definitive: there is no credential to steal. The most sophisticated credential exfiltration attack cannot extract a password that was never created.
DB_CONNECTION="Server=sql.database.windows.net;
Database=prod;User ID=appuser;
Password=S3cur3P@ssword!;
Encrypt=True;"
# Problems:
# - Password in config / Git / CI-CD
# - Manual rotation required
# - Leaked = full DB access
DB_CONNECTION="Server=sql.database.windows.net;
Database=prod;
Authentication=Active Directory Default;
Encrypt=True;"
# Benefits:
# - No credential anywhere
# - Azure rotates token automatically
# - RBAC-scoped access only
How Azure Managed Identity Works: The Authentication Flow
Understanding the token exchange flow is essential before implementing Managed Identity. The following diagram shows exactly what happens when your application code calls Azure SQL using a Managed Identity — and crucially, what does not happen (no password is ever transmitted or stored).
System-Assigned vs User-Assigned: Which Type to Use and When
Managed Identities come in two types. Choosing the correct type for each scenario is a design decision — not a technical preference. The wrong choice creates either unnecessary complexity (user-assigned where system-assigned suffices) or permission sprawl (system-assigned where user-assigned is needed).
| Factor | System-Assigned | User-Assigned |
|---|---|---|
| Lifecycle | Tied to the Azure resource. Deleted automatically when the resource is deleted. | Independent Azure resource. Survives deletion of attached resources. Must be deleted separately. |
| Sharing | One identity per resource. Cannot be shared. | One identity can be attached to multiple resources simultaneously. |
| RBAC assignment | Must be done per resource. 10 App Services = 10 RBAC assignments on Key Vault. | Assign RBAC once to the User-Assigned MI. All attached resources inherit access. |
| Best for | Single resource with unique access requirements. Most common for production VMs and App Services with dedicated Key Vaults. | Multiple resources needing identical permissions. Dev/staging/prod parity. Resources frequently replaced (containers, AKS pods). |
| IaC complexity | Simpler — enable on the resource in Bicep/Terraform, then assign RBAC. | Requires creating an Microsoft.ManagedIdentity/userAssignedIdentities resource separately, then referencing it in the compute resource and RBAC assignment. |
| Recommendation | Default choice for App Service, Azure Functions (single-environment), and VMs. | Choose for AKS workloads, multi-environment Function Apps, and any scenario with 3+ resources sharing identical permissions. |
The most common Managed Identity use case: your App Service needs to read secrets from Azure Key Vault — database passwords (for services that don't yet support Entra auth), third-party API keys, TLS certificates — without storing those secrets in application settings or config files.
Enable System-Assigned Managed Identity on the App Service
Portal: App Service → Identity → System assigned → Status: On → Save. CLI: az webapp identity assign --name YOUR-APP --resource-group YOUR-RG. Note the Object (principal) ID from the output — you need it for the RBAC assignment.
Create a Key Vault (if not already existing)
az keyvault create --name YOUR-KV --resource-group YOUR-RG --location eastus --enable-rbac-authorization true. The --enable-rbac-authorization flag switches Key Vault from the legacy access policy model to RBAC — the recommended approach in 2026. If your vault already exists with access policies, migrate to RBAC in the Key Vault → Access configuration blade.
Assign "Key Vault Secrets User" role to the App Service MI
az role assignment create --assignee OBJECT-ID-FROM-STEP-1 --role "Key Vault Secrets User" --scope /subscriptions/SUB-ID/resourceGroups/YOUR-RG/providers/Microsoft.KeyVault/vaults/YOUR-KV. This grants read-only access to secrets. Use "Key Vault Secrets Officer" if the app also needs to set or update secrets.
Use Key Vault References in App Settings (zero code change)
Replace any secret value in App Settings with a Key Vault reference: @Microsoft.KeyVault(SecretUri=https://YOUR-KV.vault.azure.net/secrets/SECRET-NAME/). The App Service resolves the reference at runtime using its Managed Identity — your application reads the setting as a normal environment variable. No SDK required.
Alternative: Read secrets in code using the SDK
Install azure-keyvault-secrets and azure-identity. Use DefaultAzureCredential() to authenticate. Shown in the code block below.
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
# DefaultAzureCredential uses Managed Identity when deployed to Azure
# and uses az login / VS Code credentials when running locally
credential = DefaultAzureCredential()
client = SecretClient(
vault_url="https://YOUR-KV.vault.azure.net",
credential=credential
)
# Fetch the secret — no password, no connection string
db_password = client.get_secret("db-password").value
api_key = client.get_secret("external-api-key").value
# Use the values normally — they are plain strings
print(f"Connected. API key retrieved: {api_key[:4]}****")
Azure SQL Database natively supports Microsoft Entra authentication. This means your Function App can connect to SQL using its Managed Identity — presenting a JWT token instead of a username and password. The SQL driver handles the token exchange transparently.
Enable Managed Identity on the Function App
az functionapp identity assign --name YOUR-FUNC --resource-group YOUR-RG. Note the principal ID from the output.
Set a Microsoft Entra Admin on the Azure SQL Server
The SQL server must have a Microsoft Entra admin before you can create Entra-backed database users. Portal: SQL Server → Microsoft Entra ID → Set admin → select your admin user or group. CLI: az sql server ad-admin create --server-name YOUR-SQL --resource-group YOUR-RG --display-name "SQL Admin" --object-id YOUR-ENTRA-USER-OBJECT-ID
Create a database user mapped to the Function App's MI
Connect to the target database as the Entra admin (not SQL auth) and run these T-SQL statements. Replace my-function-app with your actual Function App name.
Update the connection string — remove the password
Set the App Setting on the Function App: Server=tcp:YOUR-SQL.database.windows.net,1433;Database=YOUR-DB;Authentication=Active Directory Default;Encrypt=True;. The Authentication=Active Directory Default directive instructs the Microsoft.Data.SqlClient driver to use DefaultAzureCredential — no password in the string.
-- Run these in the target database, not master
-- Create the external user mapped to the MI
CREATE USER [my-function-app] FROM EXTERNAL PROVIDER;
-- Grant read + write access
ALTER ROLE db_datareader ADD MEMBER [my-function-app];
ALTER ROLE db_datawriter ADD MEMBER [my-function-app];
-- Optional: allow stored procedure execution
GRANT EXECUTE TO [my-function-app];
-- Verify
SELECT name, type_desc FROM sys.database_principals
WHERE name = 'my-function-app';
using Microsoft.Data.SqlClient;
using Azure.Identity;
public class DatabaseService
{
private readonly string _connectionString;
public DatabaseService(IConfiguration config)
{
// No password in connection string
_connectionString = config["SqlConnectionString"];
// e.g. "Server=tcp:...;Database=mydb;Authentication=Active Directory Default;Encrypt=True;"
}
public async Task<IEnumerable<string>> GetProductsAsync()
{
// SqlClient uses DefaultAzureCredential under the hood
// when Authentication=Active Directory Default is set
await using var conn = new SqlConnection(_connectionString);
await conn.OpenAsync(); // Token acquired automatically — no password
var cmd = new SqlCommand("SELECT Name FROM Products", conn);
var reader = await cmd.ExecuteReaderAsync();
var results = new List<string>();
while (await reader.ReadAsync()) results.Add(reader.GetString(0));
return results;
}
}
Azure Blob Storage supports Entra-based authentication via RBAC roles. Instead of putting a storage account key or SAS token in your application config, assign the appropriate Storage RBAC role to your App Service's Managed Identity and access blobs using DefaultAzureCredential. This also eliminates the rotation risk of storage account keys.
Assign the correct Storage RBAC role to the Managed Identity
Navigate to the Storage Account → Access Control (IAM) → Add Role Assignment. Choose: Storage Blob Data Reader (read-only), Storage Blob Data Contributor (read + write), or Storage Blob Data Owner (full control including ACLs). Assign to your App Service's Managed Identity. Scope it to the specific container if the app should only access one container.
Store only the storage account URL in App Settings
Add an App Setting: STORAGE_URL=https://YOUR-ACCOUNT.blob.core.windows.net. No account key, no SAS token, no connection string with credentials.
Use BlobServiceClient with DefaultAzureCredential in code
Install azure-storage-blob and azure-identity. Instantiate the client with the credential — shown below.
import os
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
# No account key — just the URL and DefaultAzureCredential
credential = DefaultAzureCredential()
storage_url = os.environ["STORAGE_URL"] # e.g. https://myaccount.blob.core.windows.net
blob_service = BlobServiceClient(account_url=storage_url, credential=credential)
container = blob_service.get_container_client("my-container")
# Upload a file
with open("report.pdf", "rb") as data:
container.upload_blob(name="reports/report.pdf", data=data, overwrite=True)
# Download a file
blob = container.get_blob_client("reports/report.pdf")
download = blob.download_blob()
content = download.readall()
Kubernetes pods running on AKS cannot use System-Assigned Managed Identity directly (that is assigned to the node VM, not the pod). AKS Workload Identity is the Microsoft-recommended solution: it federates a Kubernetes Service Account with a User-Assigned Managed Identity in Entra ID, allowing specific pods to acquire tokens for Azure services without any credentials in the pod spec or container image.
Enable OIDC Issuer and Workload Identity on the AKS cluster
az aks update --name YOUR-CLUSTER --resource-group YOUR-RG --enable-oidc-issuer --enable-workload-identity. Get the OIDC issuer URL: az aks show --name YOUR-CLUSTER --resource-group YOUR-RG --query "oidcIssuerProfile.issuerUrl" -o tsv
Create a User-Assigned Managed Identity
az identity create --name workload-identity --resource-group YOUR-RG. Note the clientId and principalId from the output. Assign the necessary Azure RBAC roles to this identity (e.g., Key Vault Secrets User on your Key Vault).
Create the Kubernetes Service Account with MI annotation
Create a Kubernetes Service Account annotated with the MI's client ID and configure the federated identity credential — shown in the YAML below.
Create the Federated Identity Credential
az identity federated-credential create --name aks-fed-cred --identity-name workload-identity --resource-group YOUR-RG --issuer OIDC-ISSUER-URL --subject system:serviceaccount:YOUR-NAMESPACE:YOUR-SERVICE-ACCOUNT --audiences api://AzureADTokenExchange
Reference the Service Account in your pod spec
Add serviceAccountName: YOUR-SERVICE-ACCOUNT to your Deployment pod spec. Pods using this Service Account will automatically receive a projected token that DefaultAzureCredential exchanges for Azure access tokens — no secrets mounted in the pod.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: my-namespace
annotations:
azure.workload.identity/client-id: "YOUR-USER-ASSIGNED-MI-CLIENT-ID"
labels:
azure.workload.identity/use: "true"
---
# deployment.yaml — pod references the Service Account
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: my-app-sa # Use the annotated SA
containers:
- name: app
image: myregistry.azurecr.io/my-app:latest
# No environment variables with secrets
# DefaultAzureCredential picks up the projected token automatically
DefaultAzureCredential: The One Class That Works Everywhere
DefaultAzureCredential is the Azure Identity SDK class that makes passwordless authentication work in both local development and production without any code changes between environments. It tries authentication methods in a defined sequence and uses the first one that succeeds:
For local development, run az login once in your terminal. DefaultAzureCredential will use your Azure CLI session. To simulate a specific Managed Identity locally (for testing RBAC assignments), set the environment variables AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, and AZURE_TENANT_ID for a Service Principal — DefaultAzureCredential will pick these up first in its chain. Never commit these values. Use a .env file excluded from Git.
Provisioning Managed Identity with Bicep and Terraform
Managed Identity and RBAC assignments should always be in your Infrastructure-as-Code — not configured manually in the portal. Manual configurations are invisible to code review, are not version-controlled, and are the first thing to drift in a multi-environment deployment.
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: keyVaultName
location: location
properties: {
sku: { family: 'A', name: 'standard' }
tenantId: subscription().tenantId
enableRbacAuthorization: true // RBAC model, not legacy access policies
}
}
// App Service with System-Assigned Managed Identity
resource appService 'Microsoft.Web/sites@2023-01-01' = {
name: appServiceName
location: location
identity: {
type: 'SystemAssigned' // Enables System-Assigned MI
}
properties: { serverFarmId: appServicePlan.id }
}
// RBAC: Grant the App Service MI "Key Vault Secrets User" role
var keyVaultSecretsUserRoleId = '4633458b-17de-408a-b874-0445c86b69e6'
resource kvRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, appService.id, keyVaultSecretsUserRoleId)
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions', keyVaultSecretsUserRoleId)
principalId: appService.identity.principalId // The MI's object ID
principalType: 'ServicePrincipal'
}
}
resource "azurerm_user_assigned_identity" "workload_mi" {
name = "workload-identity"
resource_group_name = var.resource_group_name
location = var.location
}
# Assign Key Vault Secrets User role to the MI
resource "azurerm_role_assignment" "kv_secrets_user" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_user_assigned_identity.workload_mi.principal_id
}
# Federated identity credential linking AKS Service Account to the MI
resource "azurerm_federated_identity_credential" "aks_fed" {
name = "aks-workload-fed-cred"
resource_group_name = var.resource_group_name
audience = ["api://AzureADTokenExchange"]
issuer = azurerm_kubernetes_cluster.main.oidc_issuer_url
parent_id = azurerm_user_assigned_identity.workload_mi.id
subject = "system:serviceaccount:my-namespace:my-app-sa"
}
Local Development Without Breaking the Passwordless Pattern
Managed Identity is only available on Azure-hosted resources. When developers work locally, they need an equivalent mechanism that does not require secrets. These are the three approaches in priority order:
- Azure CLI login (recommended for most scenarios): Run az login in your terminal. DefaultAzureCredential picks up the session automatically. No environment variables needed. This works for Key Vault, Blob Storage, Service Bus, and most Azure SDK clients.
- Visual Studio / VS Code login: DefaultAzureCredential uses the account signed in to Visual Studio or the Azure Account extension in VS Code. Useful for .NET developers who live in the IDE.
- Service Principal with environment variables (for CI/CD or team environments): Create a Service Principal with the same RBAC roles as the Managed Identity: az ad sp create-for-rbac --name local-dev-sp. Set AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID in a .env file. Add .env to .gitignore — never commit it.
Do not add ManagedIdentityCredential or ChainedTokenCredential with hard-coded fallback secrets to work around local dev issues. The correct solution is DefaultAzureCredential plus az login. Any workaround that introduces a credential for local development undermines the entire passwordless model and creates exactly the secret you were trying to eliminate.
Migration Checklist: From Connection Strings to Managed Identity
Use this checklist when migrating an existing application from hardcoded credentials to Managed Identity. Work through it in order — skipping steps is the most common cause of authentication failures during migration.
Pre-Migration
Implementation
Validation and Cleanup
Key Takeaways
Frequently Asked Questions
Related FAVRITE Articles
- The Hidden Cloud Drain: How to Find and Kill Orphaned Azure Resources Automatically
- Azure App Service Log Stream with Least Privilege: The Complete Guide
- Azure B2C Custom Policy: Single Relying Party for Separate Sign-In and Sign-Up Flows
- How to Fix Azure OpenAI Token Limits: Architectural Patterns for High-Throughput Apps