While environment variables work well for simple production deployments, enterprise-grade applications centralise secrets in a dedicated vault service โ Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault. Vault services provide secret rotation, access auditing, role-based access control, and automatic injection into the configuration pipeline. ASP.NET Core’s extensible configuration provider model makes adding vault integration a matter of adding a NuGet package and a few lines of bootstrapping code, with zero changes to how the rest of the application reads configuration.
Azure Key Vault Integration
// dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
// dotnet add package Azure.Identity
// โโ Program.cs โ add Key Vault as a configuration provider โโโโโโโโโโโโโโโโ
var builder = WebApplication.CreateBuilder(args);
// Add Key Vault only in non-Development environments
if (!builder.Environment.IsDevelopment())
{
var keyVaultUri = builder.Configuration["KeyVault:Uri"]
?? throw new InvalidOperationException("KeyVault:Uri is required.");
// DefaultAzureCredential tries: Managed Identity, VS credentials,
// Azure CLI, environment variables โ works in both CI and production
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential());
}
// After AddAzureKeyVault, secrets from Key Vault are available via IConfiguration
// Key Vault secret "JwtSettings--SecretKey" maps to config key "JwtSettings:SecretKey"
// (Key Vault secret names use -- instead of __ for hierarchy)
-- (double hyphen) in the secret name to : in the configuration key. So a Key Vault secret named JwtSettings--SecretKey is read as configuration key JwtSettings:SecretKey. AWS Secrets Manager and HashiCorp Vault have their own naming conventions โ check the provider’s documentation for the exact separator mapping.DefaultAzureCredential in the code example above uses Managed Identity automatically when running in Azure, and falls back to Azure CLI credentials when running locally. This eliminates the “secret to access secrets” problem.Custom Configuration Provider
// โโ Minimal custom provider โ for testing and special sources โโโโโโโโโโโโโ
public class DatabaseConfigSource : IConfigurationSource
{
private readonly string _connectionString;
public DatabaseConfigSource(string connectionString)
=> _connectionString = connectionString;
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new DatabaseConfigProvider(_connectionString);
}
public class DatabaseConfigProvider : ConfigurationProvider
{
private readonly string _connectionString;
public DatabaseConfigProvider(string connectionString)
=> _connectionString = connectionString;
public override void Load()
{
// Read key-value settings from a database table
using var conn = new SqlConnection(_connectionString);
conn.Open();
using var cmd = new SqlCommand("SELECT [Key], [Value] FROM AppSettings", conn);
using var reader = cmd.ExecuteReader();
while (reader.Read())
Data[reader.GetString(0)] = reader.GetString(1);
}
}
// โโ Register the custom provider โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
builder.Configuration.Add(new DatabaseConfigSource(connectionString));
Common Mistakes
Mistake 1 โ Adding vault provider before appsettings loads (wrong override order)
โ Wrong โ vault added first; appsettings.json overrides vault values (backwards).
โ Correct โ add vault provider last in the configuration pipeline so vault values take highest precedence.
Mistake 2 โ Using client secret to authenticate to Key Vault (secret rotation problem)
โ Wrong โ storing the vault client secret as an environment variable creates a bootstrap paradox.
โ Correct โ use Managed Identity or IAM roles for vault authentication; no credentials needed.