AI WisdomArchitecture & guides β†—
HT
How Things Work

Azure Key Vault & Managed Identity

Zero-secret configuration β€” how to get secrets into your app without storing credentials anywhere.

How It Works

Azure Key Vault stores secrets, certificates, and cryptographic keys in a hardware-backed, audited service. Managed Identity gives your Azure resources a cryptographic identity backed by Azure AD β€” eliminating the bootstrap credential problem. Combined, they enable zero-secret configuration: no passwords in code, config files, environment variables, or CI/CD pipelines.

1
Managed Identity removes the bootstrap secret problem

The classic credential problem: you need a secret to get a secret. Managed Identity solves this β€” Azure assigns a cryptographic identity to your resource (App Service, VM, Function) backed by Azure AD. No password, no key file, no rotation needed.

2
App requests a token from the Instance Metadata Service (IMDS)

At runtime, the Azure SDK calls http://169.254.169.254/metadata/identity/oauth2/token β€” an Azure-internal endpoint only reachable from within Azure. This returns a short-lived JWT signed by Azure AD. DefaultAzureCredential handles this automatically.

3
Key Vault validates the token and checks permissions

Key Vault receives the request with the Bearer token. It validates the signature against Azure AD and then checks: does this identity have Key Vault Secrets User RBAC role (or access policy permission) on this vault? If not, 403 Forbidden.

4
Secret is returned and should be cached

The secret value is returned over TLS. Key Vault enforces a rate limit of 5 requests per second per secret. Your code should cache the value (5 minutes is typical) β€” refresh too rarely and you pick up rotated secrets too late; refresh too often and you hit rate limits.

5
Secret rotation with versioning

Key Vault stores all versions of a secret. When you set a new value, the previous version is not deleted β€” it's retained with a 90-day soft-delete window. Apps that fetch the latest version automatically get the new value on the next cache refresh. No redeployment needed.

Key Concepts

πŸ”SecretClient

Azure SDK client for reading/writing Key Vault secrets. Pair with DefaultAzureCredential for zero-config auth that works in dev (Azure CLI) and prod (Managed Identity) without code changes.

πŸ”—DefaultAzureCredential

Credential chain that tries multiple auth sources in order: environment variables β†’ workload identity β†’ managed identity β†’ Visual Studio β†’ Azure CLI. Same code works everywhere.

πŸ€–Managed Identity

System-assigned: 1-to-1 with resource, deleted with resource. User-assigned: independent lifecycle, shareable across resources. Both eliminate the need to store any credentials.

πŸ”—Key Vault References

App Service / Functions config value syntax: @Microsoft.KeyVault(SecretUri=...). Azure resolves this using the resource's managed identity at runtime. Zero SDK code required.

πŸ—‘οΈSoft-delete & Purge Protection

Soft-delete retains deleted secrets for 7–90 days (recoverable). Purge protection prevents immediate permanent deletion, requiring the retention period to expire first. Both must be enabled to prevent accidental loss.

πŸ›‘οΈRBAC vs Access Policies

RBAC is the modern model β€” uses Azure AD roles (Key Vault Secrets User/Officer/Admin), supports Conditional Access and PIM. Access Policies is legacy, vault-level only, retiring. Always use RBAC for new vaults.

πŸ“šSecret Versioning

Every SetSecret() call creates a new version. The 'current' version is the latest active one. Applications fetching by name (without version) always get the latest, enabling zero-downtime rotation.

.NET 8 β€” Key Vault + Managed Identity: zero secrets in config
tsx
1// .NET 8 β€” Zero-secret config with Key Vault + Managed Identity
2// Program.cs
3var builder = WebApplication.CreateBuilder(args);
4
5// DefaultAzureCredential tries, in order:
6// 1. Environment variables, 2. Workload Identity, 3. Managed Identity,
7// 4. Visual Studio, 5. Azure CLI, 6. Azure PowerShell, 7. Interactive
8var credential = new DefaultAzureCredential();
9
10// Add Key Vault as a configuration provider
11builder.Configuration.AddAzureKeyVault(
12 new Uri(builder.Configuration["KeyVault:VaultUri"]!),
13 credential
14);
15
16// SecretClient for imperative access
17builder.Services.AddSingleton(_ =>
18 new SecretClient(new Uri(builder.Configuration["KeyVault:VaultUri"]!), credential));
19
20var app = builder.Build();
21
22// -----------------------------------------------
23// Fetching a secret imperatively (with caching)
24// -----------------------------------------------
25public class SecretService
26{
27 private readonly SecretClient _client;
28 private readonly IMemoryCache _cache;
29
30 public async Task<string> GetSecretAsync(string name)
31 {
32 // Cache for 5 minutes β€” balance freshness vs rate limits (5 RPS per secret)
33 return await _cache.GetOrCreateAsync($"secret:{name}", async entry =>
34 {
35 entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
36 var secret = await _client.GetSecretAsync(name);
37 return secret.Value.Value;
38 });
39 }
40}
41
42// -----------------------------------------------
43// App Service config reference (NO code needed!)
44// -----------------------------------------------
45// In App Service Application Settings, add:
46// Name: ConnectionStrings__Sql
47// Value: @Microsoft.KeyVault(SecretUri=https://contoso-prod.vault.azure.net/secrets/sql-connstr/)
48//
49// App Service resolves this at runtime using its managed identity.
50// The secret value appears as a regular config value to your app.
πŸ’‘
Why This Matters

Every secret stored in a config file, environment variable, or CI/CD pipeline is a credential leak waiting to happen β€” via git history, log files, process dumps, or stolen CI tokens. Managed Identity + Key Vault eliminates all of these attack surfaces. The app never possesses a long-lived credential; it requests short-lived tokens that expire and rotate automatically.

Common Pitfalls

⚠Soft-delete doesn't prevent permanent deletion on its own β€” purge protection must also be enabled. Without purge protection, a deleted secret can be immediately purged (destroyed forever).
⚠App Service and Functions have multiple outbound IPs. IP-based Key Vault firewall rules break on scale-out when new IPs are assigned. Use private endpoints + VNet Integration instead.
⚠Key Vault enforces a rate limit of ~5 RPS per secret per vault. Fetching the secret on every request without caching will hit this limit under load and cause 429 errors.
⚠User-assigned managed identity requires TWO steps: (1) grant RBAC on Key Vault AND (2) assign the identity to the App Service/Function/VM. Completing only step 1 gives a misleading 403 that looks like an RBAC issue.
Real-World Use Cases

1Accidental Secret Deletion Without Purge Protection

Scenario

A developer ran a cleanup script to remove unused Key Vault secrets. A production secret named db-connection was accidentally deleted. The app started throwing connection errors within seconds.

Problem

The vault had soft-delete disabled (pre-2021 default). The deletion was permanent and immediate. The secret was gone with no recovery path. The team had to manually reconstruct the connection string from a colleague's local notes.

Solution

Enabled soft-delete (now mandatory on all new vaults) and purge protection on all vaults. Implemented a policy via Azure Policy that denies Key Vault creation without purge protection. Added RBAC so developers have Key Vault Secrets User (read-only) and only the pipeline service principal has Key Vault Secrets Officer.

πŸ’‘

Takeaway: Soft-delete is now mandatory on new Azure Key Vaults, but legacy vaults may still lack it. Purge protection is a separate setting and must be explicitly enabled. Without it, a single az keyvault secret delete command causes permanent data loss.

2Key Vault Firewall Blocking App Service After IP Change

Scenario

A team enabled the Key Vault firewall to restrict access to known IPs. The app worked fine until App Service automatically scaled out, assigning new outbound IPs that weren't in the allow-list. The app started returning 403 on every secret fetch.

Problem

App Service outbound IPs are not static β€” they change when you scale out, change pricing tier, or App Service rebalances its infrastructure. A firewall rule based on the 4 'main' IPs misses the additional IPs used during scale-out events.

Solution

Replaced IP firewall rules with private endpoints (Key Vault Private Link). The Key Vault is only accessible via the VNet, and the App Service connects through VNet Integration. Alternatively, added all possible outbound IPs from the App Service properties page and set up an alert when IPs change.

πŸ’‘

Takeaway: App Service has multiple possible outbound IPs (show in portal under 'Outbound IP addresses'). Using IP firewall on Key Vault is fragile β€” use private endpoints + VNet Integration for production environments.

3User-Assigned Managed Identity Not Attached to App Service

Scenario

A team created a user-assigned managed identity, granted it Key Vault Secrets User RBAC, and updated the app code to use DefaultAzureCredential. Local dev worked (Azure CLI fallback). Deploying to App Service returned 403 on every Key Vault request.

Problem

Creating a user-assigned managed identity and granting it RBAC is insufficient. The identity must also be explicitly assigned to the App Service resource in the Identity blade under User-assigned. Without this step, the App Service has no identity to present to IMDS.

Solution

Assigned the user-assigned identity to the App Service via the Identity > User assigned > + Add flow. Added this step to the Terraform / Bicep deployment template to prevent recurrence. Added an integration test that calls Key Vault on startup and fails the health check if access is denied.

πŸ’‘

Takeaway: User-assigned identity requires two steps: (1) grant RBAC on Key Vault, (2) attach to the resource. Most teams complete step 1 and forget step 2. System-assigned identity is simpler for single-resource scenarios β€” it's created and attached atomically.