Manages secrets and sensitive config. User secrets, environment variables, rotation.
Cloud-agnostic secrets management for .NET applications. Covers the full lifecycle: user secrets for local development, environment variables for production, IConfiguration binding patterns, secret rotation, and managed identity as a production best practice. Includes anti-patterns to avoid (secrets in source, appsettings.json, hardcoded connection strings).
Cross-references: [skill:dotnet-security-owasp] for OWASP A02 (Cryptographic Failures) and deprecated pattern warnings, [skill:dotnet-csharp-configuration] for Options pattern and configuration source precedence.
| Environment | Secret Source | Mechanism |
|---|---|---|
| Local dev | User secrets | dotnet user-secrets CLI, secrets.json outside repo |
| CI/CD | Pipeline variables | Injected as environment variables, never in YAML |
| Staging/Production | Environment variables or vault | OS-level env vars, managed identity, or vault provider |
Principle: Secrets must never exist in the source repository or in any file committed to version control. Each environment tier uses the appropriate mechanism for its trust boundary.
User secrets store sensitive configuration outside the project directory in the user profile, preventing accidental commits.
# Do NOT commit real secrets; use dotnet user-secrets or env vars
# Initialize user secrets for a project (creates UserSecretsId in csproj)
dotnet user-secrets init
# Set individual secrets (placeholders shown)
# (placeholders shown; do NOT use real secrets in shell history)
dotnet user-secrets set "ConnectionStrings:DefaultDb" "Server=localhost;Database=myapp;User=sa;Password=<DB_PASSWORD_PLACEHOLDER>"
dotnet user-secrets set "Smtp:ApiKey" "<SENDGRID_API_KEY_PLACEHOLDER>"
dotnet user-secrets set "Jwt:SigningKey" "<JWT_SIGNING_KEY_PLACEHOLDER>"
# List current secrets
dotnet user-secrets list
# Remove a secret
dotnet user-secrets remove "Smtp:ApiKey"
# Clear all secrets
dotnet user-secrets clear
User secrets are stored at:
%APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json~/.microsoft/usersecrets/<UserSecretsId>/secrets.jsonThe secrets.json file is plain JSON with the same structure as appsettings.json:
{
"ConnectionStrings": {
"DefaultDb": "Server=localhost;Database=myapp;User=sa;Password=<DB_PASSWORD_PLACEHOLDER>"
},
"Smtp": {
"ApiKey": "<SENDGRID_API_KEY_PLACEHOLDER>"
},
"Jwt": {
"SigningKey": "<JWT_SIGNING_KEY_PLACEHOLDER>"
}
}
User secrets are loaded automatically by WebApplication.CreateBuilder and Host.CreateDefaultBuilder when
DOTNET_ENVIRONMENT or ASPNETCORE_ENVIRONMENT is Development:
var builder = WebApplication.CreateBuilder(args);
// User secrets are already loaded. Access them via IConfiguration:
var connectionString = builder.Configuration.GetConnectionString("DefaultDb");
For non-web hosts (console apps, worker services):
var builder = Host.CreateApplicationBuilder(args);
// User secrets are loaded automatically in Development environment.
// For explicit control:
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
Gotcha: User secrets are not encrypted -- they are just stored outside the repo. They are appropriate for development only, never for production.
Environment variables are the standard mechanism for injecting secrets into production applications without touching the filesystem.
In the default ASP.NET Core configuration stack, environment variables override file-based sources (last wins):
appsettings.jsonappsettings.{Environment}.json.NET maps environment variables to configuration keys using __ (double underscore) as the section separator:
# These environment variables map to configuration sections:
export ConnectionStrings__DefaultDb="Server=prod-db;Database=myapp;..."
export Smtp__ApiKey="<SENDGRID_API_KEY_PLACEHOLDER>"
export Jwt__SigningKey="<JWT_SIGNING_KEY_PLACEHOLDER>"
# With a prefix (recommended to avoid collisions):
export MYAPP_ConnectionStrings__DefaultDb="Server=prod-db;..."
// Load prefixed environment variables
builder.Configuration.AddEnvironmentVariables(prefix: "MYAPP_");
// Access the same way as any configuration source:
var smtpKey = builder.Configuration["Smtp:ApiKey"];
# docker-compose.yml -- inject secrets via environment