
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Hardcoded connection strings and API keys remain the most common security vulnerability I encounter during infrastructure audits, even in mature enterprise codebases. To properly manage secrets and config in .NET apps, you must decouple sensitive values from source control while maintaining a seamless developer experience across local, staging, and production environments. This guide provides the exact configuration hierarchy and tooling required to secure your application without sacrificing deployment velocity.
How do you manage secrets and config in .NET apps locally without Git?
The Microsoft.Extensions.Configuration.UserSecrets package is your first line of defense during development. It stores sensitive data outside the project tree in a JSON file located at %APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json on Windows or ~/.microsoft/usersecrets/<user_secrets_id>/secrets.json on Linux/macOS. Because this path lives outside your repository root, accidental commits are structurally impossible.
Initialize and populate User Secrets
- Right-click your project in Visual Studio and select Manage User Secrets, or run the CLI command below to generate a unique
UserSecretsIdGUID in your.csproj:
dotnet user-secrets init --project src/MyWebApp/MyWebApp.csproj - Set individual secrets using the colon-delimited key format that mirrors your configuration hierarchy:
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=localhost;Database=DevDb;Trusted_Connection=true;"
dotnet user-secrets set "PaymentGateway:ApiKey" "sk_test_dev_key_never_in_git" - Verify the stored values without opening the file directly:
dotnet user-secrets list A common mistake I see in teams new to .NET is treating User Secrets as an encrypted store. They are plaintext JSON files intended solely for local development convenience. Never use them on production servers or CI runners. For deeper context on securing pipeline credentials before they reach your app, review handling secrets in CI/CD pipelines safely.
How does the .NET configuration provider hierarchy work?
Understanding precedence is non-negotiable when you manage secrets and config in .NET apps. The framework builds configuration from multiple sources in a specific order, where later providers override earlier ones for identical keys. This design allows you to keep safe defaults in source control while letting secure external sources win at runtime.
- appsettings.json — Base configuration committed to Git. Contains non-sensitive defaults like logging levels, feature flags, and public endpoints.
- appsettings.{Environment}.json — Environment-specific overrides (e.g.,
appsettings.Production.json). Still committed, still non-sensitive. - User Secrets — Local development only. Active when
ASPNETCORE_ENVIRONMENT=Development. Overrides both JSON files above. - Environment Variables — Container and VM runtime injection. Uses double-underscore (
__) or colon delimiter for nested keys. Overrides all file-based sources. - Azure Key Vault / External Providers — Highest priority when configured. Fetches secrets at startup or via refresh. Wins over everything else.
This layered approach means your code never needs conditional logic like if (isProduction). You simply request config["ConnectionStrings:DefaultConnection"] and trust the host environment to supply the correct value. If you are deploying to Kubernetes, understanding how this interacts with platform-native secret management is critical; see Kubernetes secrets management done right for container-specific patterns.
How do you integrate Azure Key Vault with .NET configuration?
For production Azure workloads, Azure Key Vault should be your authoritative secret source. The Azure.Extensions.AspNetCore.Configuration.Secrets package adds Key Vault as a configuration provider, making secrets available through the same IConfiguration interface you already use.
Configure Key Vault in Program.cs
using Azure.Identity;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
var builder = WebApplication.CreateBuilder(args);
// Add Azure Key Vault as highest-priority provider
builder.Configuration.AddAzureKeyVault(
new Uri("https://my-app-vault.vault.azure.net/"),
new DefaultAzureCredential(),
options =>
{
options.SecretFilter = secret =>
secret.Properties.Tags.ContainsKey("Environment") &&
secret.Properties.Tags["Environment"] == builder.Environment.EnvironmentName;
});
builder.Services.Configure<PaymentSettings>(
builder.Configuration.GetSection("PaymentGateway")); Always use DefaultAzureCredential instead of client secrets or connection strings. This enables managed identity authentication in Azure and falls back to developer credentials locally without code changes. Tag-based filtering prevents staging secrets from leaking into production and reduces startup latency by limiting fetched secrets.
Bind secrets to strongly-typed options
Never access configuration via magic strings in business logic. Define a POCO class and bind it:
public class PaymentSettings
{
public string ApiKey { get; set; } = string.Empty;
public string WebhookSecret { get; set; } = string.Empty;
public int TimeoutSeconds { get; set; } = 30;
}
// In service registration
builder.Services.Configure<PaymentSettings>(
builder.Configuration.GetSection("PaymentGateway"));
// In controller/service constructor
public class CheckoutService(IOptions<PaymentSettings> options)
{
private readonly PaymentSettings _settings = options.Value;
} This pattern gives you compile-time safety, validation attributes, and testability. If you need broader observability into how these configurations affect runtime behavior, consider instrumenting your app with OpenTelemetry to trace configuration-dependent failures.
| Provider | Best For | Security Level | Cost | Refresh Support |
|---|---|---|---|---|
| User Secrets | Local development | Low (plaintext) | Free | Manual reload |
| Environment Variables | Containers, VMs, CI | Medium (host-level) | Free | Restart required |
| Azure Key Vault | Azure production workloads | High (HSM-backed) | Per-operation | Polling or push |
| AWS Secrets Manager | AWS production workloads | High (KMS-encrypted) | Per-secret + API | Lambda rotation |
| HashiCorp Vault | Multi-cloud, on-prem | Highest (dynamic) | Self-hosted/Enterprise | Dynamic leases |
What are common mistakes when managing .NET configuration?
Even experienced teams fall into predictable traps. Avoid these failures I repeatedly audit:
- Committing appsettings.Production.json with real values. Environment-specific JSON files should contain only structural placeholders or non-sensitive overrides. Actual production values belong in Key Vault or environment variables.
- Using IConfiguration directly in services. Always prefer
IOptions<T>orIOptionsSnapshot<T>. Direct dictionary access bypasses validation, creates hidden dependencies, and makes unit testing painful. - Ignoring secret rotation. Static secrets are liabilities. Use Azure Key Vault's auto-rotation policies or AWS Secrets Manager Lambda rotators. Your app should handle transient auth failures gracefully.
- Logging configuration values. Structured logging frameworks can accidentally serialize entire configuration sections. Configure redaction for any key containing "password", "secret", "key", or "token". Review structured logging best practices for safe instrumentation patterns.
- Missing validation at startup. Use
ValidateOnStart()with the Options builder to fail fast if required secrets are missing rather than discovering the gap during the first user request.
Secure Configuration Is a Deployment Discipline
When you manage secrets and config in .NET apps correctly, security becomes an architectural property rather than a developer chore. Start today by auditing your repository for hardcoded values, migrating local development to User Secrets, and wiring Azure Key Vault (or your cloud equivalent) as the authoritative production source. Enforce the pattern through CI checks that reject commits containing suspicious patterns and validate configuration completeness at startup. If your team needs hands-on guidance implementing this architecture or preparing for a compliance audit, reach out to discuss your specific environment.