.NET & C# Development · Lesson 162 of 229
Azure Key Vault in .NET — Secrets and Managed Identity
Azure Key Vault in .NET — Secrets and Managed Identity
Hardcoded secrets and connection strings in configuration files are a security risk. Azure Key Vault stores secrets centrally, and Managed Identity eliminates the need for credentials entirely.
Why Key Vault?
Without Key Vault:
appsettings.Production.json:
"ConnectionStrings": { "Default": "Server=prod;Password=SuperSecret123" }
→ Password in source control, visible in build artifacts, rotated manually
With Key Vault + Managed Identity:
No password in any file
No service principal credentials
Secrets rotate without redeploying the app
Access controlled by Azure RBACStep 1: Install Packages
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.*" />
<PackageReference Include="Azure.Identity" Version="1.*" />Step 2: Add Key Vault as a Configuration Provider
// Program.cs — add Key Vault before builder.Build()
var keyVaultUri = builder.Configuration["KeyVault:Uri"];
if (!string.IsNullOrEmpty(keyVaultUri))
{
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential()); // uses Managed Identity in Azure, local credential chain in dev
}
var app = builder.Build();// appsettings.json — only the vault URI, no secrets
{
"KeyVault": {
"Uri": "https://my-app-vault.vault.azure.net/"
}
}
// appsettings.Development.json — local dev overrides (no vault needed locally)
{
"ConnectionStrings": {
"Default": "Host=localhost;Database=myapp;Username=dev;Password=dev"
}
}Key Vault secret naming:
ASP.NET Core maps -- to : in secret names
Secret name in Key Vault: ConnectionStrings--Default
Becomes: ConnectionStrings:Default in IConfiguration
Access: builder.Configuration.GetConnectionString("Default")Step 3: DefaultAzureCredential — How Authentication Works
DefaultAzureCredential tries credential providers in order:
1. EnvironmentCredential — AZURE_CLIENT_ID, AZURE_CLIENT_SECRET env vars
2. WorkloadIdentityCredential — AKS with workload identity
3. ManagedIdentityCredential — Azure VM, App Service, Container App, AKS node MSI
4. SharedTokenCacheCredential — cached login from Visual Studio, VS Code
5. VisualStudioCredential — az login in VS
6. AzureCliCredential — az login in terminal
7. AzurePowerShellCredential — Connect-AzAccount
In Azure (production): Managed Identity is used — no credentials, no rotation
In local dev: Azure CLI or Visual Studio signed-in account// Use DefaultAzureCredential in your own Azure SDK calls too
var secretClient = new SecretClient(
new Uri("https://my-app-vault.vault.azure.net/"),
new DefaultAzureCredential());
var secret = await secretClient.GetSecretAsync("MySecret");
Console.WriteLine(secret.Value.Value);Step 4: Managed Identity Setup in Azure
# Enable system-assigned managed identity on App Service
az webapp identity assign \
--name my-app \
--resource-group my-rg
# Grant the identity access to Key Vault
az keyvault set-policy \
--name my-app-vault \
--object-id <identity-principal-id> \
--secret-permissions get list
# Modern RBAC approach (preferred over access policies)
az role assignment create \
--role "Key Vault Secrets User" \
--assignee <identity-principal-id> \
--scope /subscriptions/.../resourceGroups/.../providers/Microsoft.KeyVault/vaults/my-app-vault# Kubernetes: use workload identity instead of node MSI
# Annotate the service account
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
annotations:
azure.workload.identity/client-id: "<managed-identity-client-id>"Step 5: Reload Secrets Without Restart
// By default, Key Vault secrets are loaded once at startup
// To reload secrets periodically (e.g., after rotation):
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential(),
new AzureKeyVaultConfigurationOptions
{
ReloadInterval = TimeSpan.FromMinutes(30), // reload every 30 minutes
});// Trigger reload in response to an event (e.g., rotation webhook):
public class SecretRotationController(IConfigurationRoot config) : ControllerBase
{
[HttpPost("rotate")]
[Authorize(Policy = "InternalOnly")]
public IActionResult NotifyRotation()
{
config.Reload(); // forces Key Vault provider to refresh
return Ok();
}
}Accessing Secrets Directly (Not via IConfiguration)
// For secrets that are not connection strings (e.g., API keys used in one service)
public class PaymentService(SecretClient secretClient)
{
private string? _apiKey;
public async Task<string> GetApiKeyAsync(CancellationToken ct)
{
if (_apiKey is not null) return _apiKey;
var secret = await secretClient.GetSecretAsync("PaymentGateway-ApiKey", cancellationToken: ct);
_apiKey = secret.Value.Value;
return _apiKey;
}
}
// Register SecretClient in DI
builder.Services.AddSingleton(sp =>
new SecretClient(
new Uri(builder.Configuration["KeyVault:Uri"]!),
new DefaultAzureCredential()));Local Development — No Azure Required
// Option 1: Use user secrets (dotnet user-secrets)
// Stored in ~/.microsoft/usersecrets/<project-id>/secrets.json
// Never in source control
dotnet user-secrets set "ConnectionStrings:Default" "Host=localhost;Database=myapp"
dotnet user-secrets set "PaymentGateway:ApiKey" "test-key-abc"
// Access: builder.Configuration["ConnectionStrings:Default"] — same code
// Option 2: Conditional vault usage
if (builder.Environment.IsProduction() || builder.Environment.IsStaging())
{
builder.Configuration.AddAzureKeyVault(
new Uri(builder.Configuration["KeyVault:Uri"]!),
new DefaultAzureCredential());
}
// In dev: appsettings.Development.json + user-secrets take precedenceInterview Answer
"Azure Key Vault is added as an ASP.NET Core configuration provider with AddAzureKeyVault — it populates IConfiguration like any other source, so existing code reading configuration["ConnectionStrings:Default"] works unchanged; Key Vault secret names use -- as a separator (ConnectionStrings--Default becomes ConnectionStrings:Default). Authentication uses DefaultAzureCredential — in Azure it picks up the Managed Identity automatically, no credentials or service principal secrets required. Locally it falls back to az login or Visual Studio sign-in. Managed Identity requires assigning the Key Vault Secrets User RBAC role to the application's identity on the vault. Secrets are loaded at startup by default; set ReloadInterval to refresh them periodically after rotation without restarting. For local development, combine user-secrets (never committed) with conditional vault registration so the app works without Azure access."