Custom Configuration Providers and Azure Key Vault

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)
Note: Azure Key Vault secret names cannot contain underscores or colons โ€” they use hyphens and double hyphens for hierarchy. The Azure Key Vault configuration provider maps -- (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.
Tip: Use Managed Identity (Azure) or IAM roles (AWS) for vault authentication in production rather than storing vault credentials in environment variables. With Managed Identity, your Azure App Service or Container App automatically authenticates to Key Vault without any stored credentials at all โ€” the Azure platform handles the identity. 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.
Warning: Key Vault secrets are loaded at application startup by default โ€” changes to vault values require a restart to take effect (unless you implement a change detection polling mechanism). For secrets that change frequently (short-lived tokens, rotated keys), implement a refresh mechanism or use a configuration provider that supports hot-reload. For most production deployments, loading at startup is acceptable โ€” secret rotation triggers a rolling restart of application instances.

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.

🧠 Test Yourself

An Azure Key Vault secret is named JwtSettings--SecretKey. What configuration key does it map to in IConfiguration?