Azure Key Vault & Managed Identity
Key Points
- Key Vault stores secrets, keys, certificates. RBAC or vault access policies.
- Managed Identity is Azure AD identity for an Azure resource (App Service, Function, Container App, VM, AKS pod). System-assigned (tied to resource lifecycle) or User-assigned (independent; reusable across resources).
- Combination: app uses managed identity → reads secrets from Key Vault → no secrets in config.
- DefaultAzureCredential auto-discovers credentials in dev (CLI, VS) and prod (managed identity).
- See also Secrets Management.
Concepts (deep dive)
Why these two services exist together
The historical problem these services solve is "secrets in config files." Connection strings, API keys, certificates, signing keys — they ended up in appsettings.json, in environment variables that leaked into screenshots, in git history that nobody could purge, and in keyboards-passwords-to-junior-devs handoffs. Key Vault and Managed Identity together replace that with zero-secret access:
Old world New world
───────── ─────────
appsettings.json Key Vault
├─ DbConn = "...; Pwd=..." ├─ DbConn (RBAC: Secrets User)
├─ ApiKey = "..." └─ ApiKey
└─ JwtSigningKey = "..." ▲
│ "I am <app's managed identity>"
│ (token from Azure metadata service,
│ auto-rotated, never seen by code)
│
┌─────┴──────────┐
│ Your app │
│ (App Service, │
│ Function, │
│ Container │
│ App, AKS pod)│
└────────────────┘
- Key Vault is the vault: hardware-backed (HSM-grade in Premium) storage for secrets, keys, and certificates, with RBAC, audit, soft-delete, and versioning. Anything you wouldn't paste in a Slack channel belongs here.
- Managed Identity is the credential: an Entra ID identity automatically attached to an Azure resource. The runtime reads a short-lived token from a local metadata endpoint; the token authenticates the app to Key Vault (and Storage, SQL, Service Bus, …) over RBAC. No password ever exists in the app's process.
The combination eliminates the failure modes of secret rotation (the platform rotates the identity's token; you only rotate the backing secret, never the credential the app uses). It's the single highest-leverage security improvement you can make in an Azure app.
Key Vault
az keyvault create -g rg -n mykv --enable-rbac-authorization
az keyvault secret set --vault-name mykv -n DbConnectionString --value "..."
Three object types: - Secrets: arbitrary strings (connection strings, API keys). - Keys: cryptographic keys (RSA, EC). Sign/encrypt operations. - Certificates: X.509 certs.
RBAC vs access policies
- RBAC (recommended in 2026): standard Azure RBAC roles like "Key Vault Secrets User". Audit via Azure activity log.
- Access policies (legacy): per-principal allow lists.
App reads secrets
var client = new SecretClient(
new Uri("https://mykv.vault.azure.net/"),
new DefaultAzureCredential());
var secret = await client.GetSecretAsync("DbConnectionString");
var connStr = secret.Value.Value;
Or as config provider:
builder.Configuration.AddAzureKeyVault(new Uri(vaultUri), new DefaultAzureCredential());
var connStr = builder.Configuration["DbConnectionString"]; // transparently fetched
Managed Identity
az webapp identity assign -g rg -n myapp # system-assigned
az identity create -g rg -n myidentity # user-assigned
// In app:
var credential = new DefaultAzureCredential();
// Auto-discovers managed identity on Azure resources
DefaultAzureCredential chain
DefaultAzureCredential is the SDK's "figure out who I am" helper. It walks a list of credential providers in order and uses the first one that succeeds — so the same line of code works in dev (you're az login'd as yourself) and in prod (the app is a managed identity) without environment-specific branching. The trade-off: the chain is probing, which means a misconfigured environment can silently fall through to a slower or weaker credential. For prod code, consider pinning to ManagedIdentityCredential directly.
Tries in order: 1. EnvironmentCredential (env vars) 2. WorkloadIdentityCredential (AKS workload identity) 3. ManagedIdentityCredential (Azure resource identity) 4. AzureCliCredential (az login) 5. AzurePowerShellCredential 6. VisualStudioCredential 7. InteractiveBrowserCredential
Same code in dev (CLI logged in) and prod (managed identity). Magic.
Specific credentials
For tighter control. Avoids the surprise of falling through to interactive in prod.
User-assigned vs system-assigned
| Aspect | System | User |
|---|---|---|
| Lifecycle | Tied to resource | Independent |
| Sharing | One resource | Many resources |
| Recreation | Recreates with resource | Survives |
| When | Single-use | Reusable, AKS, multiple apps |
Workload Identity (AKS)
metadata:
annotations:
azure.workload.identity/client-id: <aad-app-id>
spec:
serviceAccountName: app-sa
Pod service account ↔ AAD app. No secrets in pod.
Connection strings via Key Vault references
# App Service config
az webapp config appsettings set -n myapp --settings \
ConnectionString="@Microsoft.KeyVault(SecretUri=https://mykv.vault.azure.net/secrets/Db/abc)"
App Service auto-resolves at runtime; managed identity reads the secret.
Secret rotation
// IOptionsMonitor picks up rotated secret
public class C(IOptionsMonitor<DbOptions> opts) { /* ... */ }
Combine with Key Vault config provider's ReloadInterval.
builder.Configuration.AddAzureKeyVault(uri, cred,
new AzureKeyVaultConfigurationOptions { ReloadInterval = TimeSpan.FromMinutes(5) });
For zero-downtime DB credential rotation: dual-key window.
Keys (cryptographic)
var keyClient = new KeyClient(uri, new DefaultAzureCredential());
var keyVaultKey = await keyClient.GetKeyAsync("my-rsa-key");
var crypto = new CryptographyClient(keyVaultKey.Value.Id, new DefaultAzureCredential());
var encrypted = await crypto.EncryptAsync(EncryptionAlgorithm.RsaOaep, plaintext);
Key never leaves Key Vault. Managed HSM tier for FIPS 140-3 Level 3.
Certificates
var certClient = new CertificateClient(uri, new DefaultAzureCredential());
var cert = await certClient.GetCertificateAsync("mycert");
App Service can mount KV-stored cert directly to TLS endpoint.
Pricing
- Standard: per-operation. ~$0.03 / 10K ops.
- Premium: HSM-backed; higher cost.
- Managed HSM: FIPS 140-3 L3 isolated HSM.
For most apps, Standard. Compliance/regulated → Premium or Managed HSM.
Networking
- Public (default).
- Service endpoint (VNet allow-list).
- Private endpoint (VNet-only access).
Production: private endpoint.
Soft delete + purge protection
Deleted items recoverable for 90 days. Purge protection prevents permanent delete during retention. Enable both.
Audit
Diagnostic Settings → Log Analytics → KQL on AuditEvent.
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where OperationName == "SecretGet"
| summarize count() by identity_claim_appid_g, ResultSignature
Find rogue access patterns.
Common mistakes
- No managed identity — secrets in config.
- Access policies vs RBAC mixed — confusing.
- Soft delete disabled — accidental purge irrecoverable.
- No private endpoint — public access in prod.
- DefaultAzureCredential falling through to interactive in prod (silent hang).
Cross-tenant scenarios
For multi-tenant apps: - User-assigned identity. - Federated Identity Credential — workload identity for cross-cloud (GitHub Actions, K8s).
Federated Identity Credentials (FIC)
az identity federated-credential create --identity-name mi --resource-group rg \
--name github-actions --issuer https://token.actions.githubusercontent.com \
--subject "repo:org/repo:ref:refs/heads/main" --audience api://AzureADTokenExchange
GitHub Actions authenticates to Azure AD without storing secrets. Same for AKS workload identity.
Code: correct vs wrong
❌ Wrong: secret in app settings
✅ Correct: Key Vault reference
❌ Wrong: vault access via key
✅ Correct: managed identity
Design patterns for this topic
Pattern 1 — "Managed identity + Key Vault"
- Intent: zero-secret cloud auth.
Pattern 2 — "DefaultAzureCredential everywhere"
- Intent: same code dev/prod.
Pattern 3 — "RBAC over access policies"
- Intent: standard Azure model.
Pattern 4 — "Soft delete + purge protection"
- Intent: safety.
Pattern 5 — "Federated Identity for CI/CD"
- Intent: GitHub Actions without secrets.
Pros & cons / trade-offs
| Aspect | Pros | Cons |
|---|---|---|
| Key Vault | Centralized; audited | Network dep |
| Managed Identity | No secrets | Azure-only |
| User-assigned | Reusable | More to manage |
| RBAC | Standard model | Migration from access policies |
When to use / when to avoid
- Always managed identity over connection strings.
- Use Key Vault for any secret you can't eliminate.
- Use Premium / Managed HSM for compliance.
- Avoid access policies in new vaults.
Interview Q&A
Q1. System-assigned vs user-assigned identity? System: tied to resource. User: independent; reusable.
Q2. DefaultAzureCredential? Chain that auto-picks the right credential — managed identity in prod, CLI/VS in dev.
Q3. Why prefer RBAC over access policies? Standard Azure model; activity log audit; familiar.
Q4. Key Vault references in App Service? @Microsoft.KeyVault(SecretUri=...) — auto-resolved with managed identity.
Q5. Soft delete + purge protection? Recover deleted items; prevent permanent purge during retention.
Q6. Workload Identity (AKS)? Pod service account ↔ AAD app. No secrets in pod.
Q7. Federated Identity Credentials? External issuers (GitHub Actions, K8s) authenticate to Azure AD without secrets.
Q8. Key vs Secret? Key: crypto operations (sign/encrypt). Secret: arbitrary string. Key never leaves vault.
Q9. Managed HSM? Single-tenant HSM-backed Key Vault. FIPS 140-3 L3.
Q10. Audit Key Vault access? Diagnostic Settings → Log Analytics. KQL queries.
Q11. Reload secrets without restart? Key Vault config provider reload interval + IOptionsMonitor.
Q12. Common pitfall in dev? DefaultAzureCredential falling through to interactive auth — silent hangs. Use ChainedTokenCredential in prod.
Gotchas / common mistakes
- ⚠️ Secrets in config — forever leaked.
- ⚠️ No purge protection — accidental loss.
- ⚠️ DefaultAzureCredential interactive in prod.
- ⚠️ Public Key Vault in regulated env.
- ⚠️ Access policies + RBAC mixed — confused permissions.