This is the first in a set of three articles about Azure Key Vault. Here we cover integration with the configuration system. In the second part, we will cover secret and certificate rotation, and in the third, granular access through RBAC and Key Vault References.
Why Key Vault and not just environment variables
In the article about Managed Identity we eliminated secrets for Azure resources — the application accesses Cosmos DB and Blob Storage without any stored credentials. But there is always a residue: API keys for third-party services (Stripe, SendGrid, Twilio), credentials for legacy systems that do not support token-based auth, TLS certificates managed centrally.
Environment variables in App Service are an acceptable solution for a single service. They become a problem when you have multiple:
- The same secret replicated in Application Settings for 5 App Services — when you rotate it, you have to change it in 5 places
- There is no audit trail — you don’t know who read or modified an environment variable
- There is no versioning — you can’t revert to a previous version of a secret
- There are no expiration notifications — a certificate can expire without anyone knowing
Azure Key Vault solves all these. It is a managed service dedicated exclusively to storing and managing secrets, cryptographic keys, and certificates — with full audit, versioning, expiration policies, and native integration with the Azure ecosystem.
Creating a Key Vault
# Create Key Vault
az keyvault create \
--name my-app-keyvault \
--resource-group my-rg \
--location westeurope \
--sku standard \
--enable-rbac-authorization true # important: we use RBAC, not Access Policies
# Check that RBAC is enabled
az keyvault show \
--name my-app-keyvault \
--resource-group my-rg \
--query properties.enableRbacAuthorization
The parameter --enable-rbac-authorization true is important. Key Vault supports two access models: Access Policies (the old model) and RBAC (the recommended model). RBAC is consistent with the rest of the Azure ecosystem, allows granular access at the individual secret level, and integrates with Azure Policy and Privileged Identity Management. Detailed role configuration is covered in the third part.
Integration with IConfiguration through AddAzureKeyVault
The simplest way to use Key Vault in ASP.NET Core is as a configuration provider. Secrets become accessible through IConfiguration, just like any other value from appsettings.
dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
dotnet add package Azure.Identity
// Program.cs
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsProduction())
{
var kvUri = builder.Configuration["KeyVaultUri"]
?? throw new InvalidOperationException("KeyVaultUri is not configured");
builder.Configuration.AddAzureKeyVault(
new Uri(kvUri),
new DefaultAzureCredential());
}
// After this line, secrets from Key Vault are accessible
// through builder.Configuration["SecretName"]
// or through IOptions with BindConfiguration
Naming convention in Key Vault: since Key Vault does not accept the : character in secret names, hierarchical configuration uses -- (double dash) as a separator. Thus Stripe--ApiKey in Key Vault becomes Stripe:ApiKey in ASP.NET Core.
# Add secrets to Key Vault
az keyvault secret set \
--vault-name my-app-keyvault \
--name "Stripe--ApiKey" \
--value "sk_live_..."
az keyvault secret set \
--vault-name my-app-keyvault \
--name "SendGrid--ApiKey" \
--value "SG.xxx..."
// Consume through IOptions -- recommended over direct reading
public class StripeOptions
{
[Required]
public string ApiKey { get; set; } = default!;
}
builder.Services.AddOptions()
.BindConfiguration("Stripe")
.ValidateDataAnnotations()
.ValidateOnStart();
// In service:
public class StripeService
{
private readonly StripeOptions _options;
public StripeService(IOptions<StripeOptions> options)
=> _options = options.Value;
}
Startup validation (ValidateOnStart) immediately tells you at deploy time if a secret is missing from Key Vault, instead of finding out only at the first request that uses it.
Prefix filtering — Key Vaults per environment
When you have multiple environments (Development, Staging, Production) and want to store their secrets in the same Key Vault, you can use prefix filtering. Secrets with the prefix Production-- are visible only in the Production environment:
# Secrets with prefix per environment
az keyvault secret set --vault-name my-app-keyvault \
--name "Production--Stripe--ApiKey" --value "sk_live_..."
az keyvault secret set --vault-name my-app-keyvault \
--name "Staging--Stripe--ApiKey" --value "sk_test_..."
// Custom manager for prefix filtering
public class EnvironmentPrefixKeyVaultManager : KeyVaultSecretManager
{
private readonly string _prefix;
public EnvironmentPrefixKeyVaultManager(string environment)
=> _prefix = $"{environment}--";
// Load only secrets with the current environment prefix
public override bool Load(SecretProperties secret)
=> secret.Name.StartsWith(_prefix, StringComparison.OrdinalIgnoreCase);
// Remove the prefix from the configuration key name
public override string GetKey(KeyVaultSecret secret)
=> secret.Name[_prefix.Length..]
.Replace("--", ConfigurationPath.KeyDelimiter);
}
// Usage in Program.cs
builder.Configuration.AddAzureKeyVault(
new Uri(kvUri),
new DefaultAzureCredential(),
new AzureKeyVaultConfigurationOptions
{
Manager = new EnvironmentPrefixKeyVaultManager(
builder.Environment.EnvironmentName)
});
Now each environment sees only its own secrets, and the prefix is automatically removed from the key — the application code remains identical regardless of environment.
What’s next
In the second part we will see how to implement automatic secret rotation without application restart (ReloadInterval, IOptionsSnapshot, IOptionsMonitor) and how to manage X.509 certificates in Key Vault with automatic renewal.
In the third part we cover granular access through RBAC at the individual secret level, full audit trail, and Key Vault References in App Service.
Questions? Write to me at contact@ludoprogramming.com.