← Back to all posts

Using Secrets in .NET

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:

  1. dotnet user-secrets for local development only
  2. environment variables for containerized and CI/CD scenarios
  3. 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.json for 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.UserSecrets version 10.0.6
  • Azure.Identity version 1.21.0
  • Azure.Extensions.AspNetCore.Configuration.Secrets version 1.5.0
  • Azure.Security.KeyVault.Secrets version 4.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