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.
# Initialize user secrets for a project (creates UserSecretsId in csproj)
dotnet user-secrets init
# Set individual secrets
dotnet user-secrets set "ConnectionStrings:DefaultDb" "Server=localhost;Database=myapp;User=sa;Password=dev123"
dotnet user-secrets set "Smtp:ApiKey" "SG.dev-key-here"
dotnet user-secrets set "Jwt:SigningKey" "dev-signing-key-min-32-chars-long!!"
# List current secrets
dotnet user-secrets list
# Remove a secret
dotnet user-secrets remove "Smtp:ApiKey"
# Clear all secrets
dotnet user-secrets clear
```text
### How It Works
User secrets are stored at:
- **Windows:** `%APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json`
- **macOS/Linux:** `~/.microsoft/usersecrets/<UserSecretsId>/secrets.json`
The `secrets.json` file is plain JSON with the same structure as `appsettings.json`:
```json
{
"ConnectionStrings": {
"DefaultDb": "Server=localhost;Database=myapp;User=sa;Password=dev123"
},
"Smtp": {
"ApiKey": "SG.dev-key-here"
},
"Jwt": {
"SigningKey": "dev-signing-key-min-32-chars-long!!"
}
}
```text
### Loading in Code
User secrets are loaded automatically by `WebApplication.CreateBuilder` and `Host.CreateDefaultBuilder` when `DOTNET_ENVIRONMENT` or `ASPNETCORE_ENVIRONMENT` is `Development`:
```csharp
var builder = WebApplication.CreateBuilder(args);
// User secrets are already loaded. Access them via IConfiguration:
var connectionString = builder.Configuration.GetConnectionString("DefaultDb");
```text
For non-web hosts (console apps, worker services):
```csharp
var builder = Host.CreateApplicationBuilder(args);
// User secrets are loaded automatically in Development environment.
// For explicit control:
if (builder.Environment.IsDevelopment())
{
builder.Configuration.AddUserSecrets<Program>();
}
```text
**Gotcha:** User secrets are not encrypted -- they are just stored outside the repo. They are appropriate for development only, never for production.
---
## Environment Variables (Production)
Environment variables are the standard mechanism for injecting secrets into production applications without touching the filesystem.
### Configuration Precedence
In the default ASP.NET Core configuration stack, environment variables override file-based sources (last wins):
1. `appsettings.json`
2. `appsettings.{Environment}.json`
3. User secrets (Development only)
4. **Environment variables** (overrides all above)
5. Command-line arguments
### Mapping Convention
.NET maps environment variables to configuration keys using `__` (double underscore) as the section separator:
```bash
# These environment variables map to configuration sections:
export ConnectionStrings__DefaultDb="Server=prod-db;Database=myapp;..."
export Smtp__ApiKey="SG.production-key"
export Jwt__SigningKey="production-signing-key-256-bits"
# With a prefix (recommended to avoid collisions):
export MYAPP_ConnectionStrings__DefaultDb="Server=prod-db;..."
```text
```csharp
// Load prefixed environment variables
builder.Configuration.AddEnvironmentVariables(prefix: "MYAPP_");
// Access the same way as any configuration source:
var smtpKey = builder.Configuration["Smtp:ApiKey"];
```text
### Container Environments
```yaml
# docker-compose.yml -- inject secrets via environment