Secrets โ database connection strings, API keys, JWT signing keys, payment processor credentials โ must never appear in source control. ASP.NET Core provides two mechanisms for keeping secrets out of committed files: User Secrets for local development (stored outside the project in the user’s profile directory) and environment variables for staging and production (set at the infrastructure level, never in code). Together these two mechanisms provide secret management for every environment without a single sensitive value in the repository.
User Secrets โ Local Development
// โโ Setup: initialise User Secrets for a project โโโโโโโโโโโโโโโโโโโโโโโโโโ
// dotnet user-secrets init --project src/BlogApp.Api
// Adds: <UserSecretsId>guid-here</UserSecretsId> to .csproj
// โโ Set secrets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// dotnet user-secrets set "JwtSettings:SecretKey" "my-super-secret-key-32chars"
// dotnet user-secrets set "ConnectionStrings:Default" "Server=.;Database=BlogApp;..."
// dotnet user-secrets set "Smtp:Password" "SMTP_API_KEY_HERE"
// โโ List and remove secrets โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// dotnet user-secrets list # show all
// dotnet user-secrets remove "Smtp:Password" # remove one
// dotnet user-secrets clear # remove all
// โโ Storage location (outside the project โ never committed) โโโโโโโโโโโโโโ
// Windows: %APPDATA%\Microsoft\UserSecrets\{UserSecretsId}\secrets.json
// Linux/macOS: ~/.microsoft/usersecrets/{UserSecretsId}/secrets.json
// โโ In code โ User Secrets load automatically in Development โโโโโโโโโโโโโโ
// WebApplication.CreateBuilder adds User Secrets when IsDevelopment() is true
// No code change needed โ secrets are available via IConfiguration as normal:
var key = builder.Configuration["JwtSettings:SecretKey"]; // reads from User Secrets in Dev
UserSecretsId GUID from your .csproj. This file lives outside your project directory (in the user’s home directory) so it is impossible to accidentally commit it. The UserSecretsId in the .csproj (just a GUID) can safely be committed โ it is how the runtime finds the secrets file for your project. Each developer on the team stores their own copy of secrets locally, which also means each developer can use their own API keys without sharing them through the repository.secrets-template.json file in the repository root that lists all required secrets with placeholder values and instructions. New team members use it as a reference: { "JwtSettings:SecretKey": "REPLACE_WITH_32_CHAR_SECRET", "ConnectionStrings:Default": "REPLACE_WITH_LOCAL_DB_STRING" }. This documents which secrets need to be set without revealing their values. Some teams add a scripts/setup-secrets.sh script that walks through the setup interactively.ASPNETCORE_ENVIRONMENT=Development). They are NOT loaded in Staging or Production even if the project has a UserSecretsId. This is intentional โ production secrets must come through proper secret management (environment variables, vault). Never rely on User Secrets in deployed environments.Environment Variables for Production
// โโ Environment variables override appsettings.json โโโโโโโโโโโโโโโโโโโโโโโ
// The colon (:) in config keys becomes double underscore (__) in env vars:
// Config key: "JwtSettings:SecretKey"
// Env var: "JwtSettings__SecretKey" (double underscore on Linux/macOS)
// Env var: "JwtSettings:SecretKey" (colon works on Windows only)
// โโ Setting env vars in different environments โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// Linux/macOS shell:
// export JwtSettings__SecretKey="prod-secret-key"
// export ConnectionStrings__Default="Server=prod-sql;Database=BlogApp;..."
// Docker Compose:
// environment:
// - JwtSettings__SecretKey=${JWT_SECRET}
// - ConnectionStrings__Default=${DB_CONNECTION_STRING}
// Kubernetes (via ConfigMap or Secret):
// env:
// - name: JwtSettings__SecretKey
// valueFrom:
// secretKeyRef:
// name: blogapp-secrets
// key: jwt-secret-key
// Azure App Service (Application Settings):
// JwtSettings__SecretKey = <value from Key Vault reference>
// โโ Reading in code โ identical to appsettings.json โโโโโโโโโโโโโโโโโโโโโโโ
// No code change needed โ IConfiguration merges all sources transparently
var secret = builder.Configuration["JwtSettings:SecretKey"];
// Works whether value came from appsettings.json, User Secrets, or env var
Common Mistakes
Mistake 1 โ Using colon separator in environment variables on Linux (silently fails)
โ Wrong โ colon in env var name is invalid on Linux; the configuration key is never set:
// export JwtSettings:SecretKey="value" โ Linux ignores this silently!
โ
Correct โ use double underscore on Linux/macOS/Docker: JwtSettings__SecretKey.
Mistake 2 โ Relying on User Secrets in non-Development environments
โ Wrong โ User Secrets do not load in Staging or Production; services use empty/null config values.
โ Correct โ use environment variables or vault for all non-Development environments.