Using Secrets in .NET
Reading Time: 7 minutes
The Problem
Most secret leaks in .NET apps are not caused by advanced attacks. They happen because of everyday habits:
- API keys stored in
appsettings.json - connection strings copied into source code
- production secrets reused in local environments
- secrets logged accidentally during debugging
A single commit can expose credentials to your entire Git history.
{
"ConnectionStrings": {
"Default": "Server=prod;Database=Orders;User Id=sa;Password=P@ssw0rd!"
},
"Stripe": {
"SecretKey": "sk_live_..."
}
}
Even if that file is removed later, the secret can still be recovered from history, build logs, forks, or CI artifacts.
The Solution
Use a layered secrets strategy in .NET:
dotnet user-secretsfor local development only- environment variables for containerized and CI/CD scenarios
- Azure Key Vault for production-grade secret storage and access control
This keeps secrets out of source control while giving each environment a secure and practical way to provide credentials.
Description
1. Use the .NET configuration pipeline correctly
ASP.NET Core configuration is provider-based, and provider order matters. A clean pattern is:
appsettings.jsonfor non-secret defaults- User Secrets in Development
- environment variables for deployment overrides
- Azure Key Vault for centralized production secrets
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
// Optional: non-secret defaults come from appsettings.json by default.
if (builder.Environment.IsDevelopment())
{
// Loads values from the local secrets store (not source-controlled).
builder.Configuration.AddUserSecrets<Program>(optional: true);
}
var keyVaultUri = builder.Configuration["KeyVault:Uri"];
if (!string.IsNullOrWhiteSpace(keyVaultUri))
{
builder.Configuration.AddAzureKeyVault(
new Uri(keyVaultUri),
new DefaultAzureCredential());
}
builder.Services.AddOptions<StripeOptions>()
.Bind(builder.Configuration.GetSection(StripeOptions.SectionName));
var app = builder.Build();
app.Run();
public sealed class StripeOptions
{
public const string SectionName = "Stripe";
public string SecretKey { get; set; } = string.Empty;
}
2. Keep local secrets local with User Secrets
Initialize secrets for a project:
dotnet user-secrets init
Set values without writing them to your repo:
dotnet user-secrets set "Stripe:SecretKey" "sk_test_..."
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=OrdersDev;User Id=dev;Password=dev-password"
This writes to a local machine store associated with your project, not to appsettings.json.
3. Use environment variables in CI/CD and containers
Environment variables are useful for ephemeral environments and pipelines:
$env:Stripe__SecretKey = "sk_test_from_pipeline"
$env:ConnectionStrings__Default = "Server=tcp:sql.example;Database=Orders;User ID=app;Password=..."
In .NET, double underscore (__) maps to nested keys (:).
4. Use Azure Key Vault in production
For hosted environments, Key Vault plus managed identity is the most robust pattern:
- app identity authenticates using
DefaultAzureCredential - no client secret stored in app config
- secrets can be rotated in Key Vault without code changes
- access is controlled with RBAC and audited
When storing hierarchical settings in Key Vault, use -- in secret names (for example, Stripe--SecretKey). The provider maps them back to configuration sections.
5. Use current NuGet package versions
As of April 16, 2026, these are current stable versions:
Microsoft.Extensions.Configuration.UserSecretsversion10.0.6Azure.Identityversion1.21.0Azure.Extensions.AspNetCore.Configuration.Secretsversion1.5.0Azure.Security.KeyVault.Secretsversion4.10.0
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.6" />
<PackageReference Include="Azure.Identity" Version="1.21.0" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.5.0" />
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.10.0" />
</ItemGroup>
6. Add guardrails to avoid accidental leaks
- never log raw secret values
- restrict who can read production secrets
- rotate keys after incidents or personnel changes
- scan commits and CI logs for leaked credentials
- prefer short-lived credentials and managed identities where possible
Summary
Using secrets in .NET is mostly about discipline and configuration order:
- User Secrets for local dev
- environment variables for deployment/runtime overrides
- Azure Key Vault for production security and operations
This model keeps credentials out of your repository and gives you a path to safer rotation, auditing, and access control as your system grows.
References
- Safe storage of app secrets in development in ASP.NET Core: https://learn.microsoft.com/aspnet/core/security/app-secrets
- Configuration in ASP.NET Core: https://learn.microsoft.com/aspnet/core/fundamentals/configuration
- Azure Key Vault configuration provider in ASP.NET Core: https://learn.microsoft.com/aspnet/core/security/key-vault-configuration
- Azure Identity client library for .NET: https://learn.microsoft.com/dotnet/api/azure.identity
- Azure Key Vault secrets client library for .NET: https://learn.microsoft.com/dotnet/api/overview/azure/security.keyvault.secrets-readme