Managing Secrets in .NET Applications: From Development to Production

Handling secrets securely is a critical part of building .NET applications. Whether it’s database credentials, API keys, or SSL certificate passwords, secrets should never be hardcoded in source code. Instead, they should be stored securely and retrieved at runtime. This blog post covers:

  • Why secrets management is important
  • What types of secrets should be stored securely
  • Setting secrets in development
  • Setting secrets in production (Azure Key Vault)
  • Reading secrets in `Program.cs`
  • How secrets work in CI/CD pipelines (Azure/GitHub)
  • Caveats and best practices

Why Secrets Management Matters

Hardcoding secrets in your codebase introduces security risks, such as:

  • Exposure in version control: If credentials are committed to Git, anyone with repository access can see them.
  • Harder to rotate credentials: Updating a secret would require modifying and redeploying the application.
  • Environment inconsistencies: Different environments (dev, staging, production) require different secrets.

A secure approach to secrets management ensures:

  • - Secrets are never stored in the codebase.
  • - They are loaded dynamically at runtime.
  • - Production and development environments remain isolated.

What Should Be Stored in Secrets?

Secrets typically include:

  • API keys (e.g., third-party services, Stripe, Twilio)
  • Database connection strings
  • Authentication credentials (e.g., OAuth tokens, JWT signing keys)
  • SSL/TLS certificate passwords (e.g., `Kestrel:Certificates:Development:Password`)
  • Encryption keys
  • Sensitive values should not be stored in `appsettings.json` for security reasons.

Setting Secrets in Development (User Secrets)

.NET provides the dotnet user-secrets tool to store sensitive information outside the codebase. These secrets are saved in a local JSON file (`secrets.json`) but are not part of the project.

Steps to Store a Secret in Development

1. Navigate to the project folder (where `.csproj` is located).

2. Initialize user-secrets (if not done already):

dotnet user-secrets init

3. Set a secret:

dotnet user-secrets set "Kestrel:Certificates:Development:Password" "your-dev-password"

 4. Verify stored secrets:

dotnet user-secrets list

Reading the Secret in `Program.cs`

In `.NET`, you can retrieve secrets using `Configuration`:

var certPassword = builder.Configuration["Kestrel:Certificates:Development:Password"];

Console.WriteLine($"Certificate Password: {certPassword}");

This will fetch the value stored in user-secrets during development.

Setting Secrets in Production (Azure Key Vault)

For production, Azure Key Vault is a secure way to store and manage secrets. Unlike local development, secrets are stored centrally and managed securely by Azure.

Steps to Store a Secret in Azure Key Vault

1. Create a Key Vault (if you don’t have one):

az keyvault create --name MyKeyVault --resource-group MyResourceGroup --location eastus

2. Store a secret: 

az keyvault secret set --vault-name MyKeyVault --name "Kestrel--Certificates--Development--Password" --value "secure-production-password"

  *Note: Azure Key Vault uses `--` instead of `:` in secret names.*

Configuring the Application to Read from Azure Key Vault

Modify `Program.cs` to load secrets from Key Vault:

using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
    builder.Configuration.AddUserSecrets<Program>();
}
else
{
    var keyVaultUrl = builder.Configuration["KeyVault:Url"];
    if (!string.IsNullOrEmpty(keyVaultUrl))
    {
        builder.Configuration.AddAzureKeyVault(
            new Uri(keyVaultUrl),
            new DefaultAzureCredential()
        );
    }
}
var certPassword = builder.Configuration["Kestrel:Certificates:Development:Password"];
Console.WriteLine($"Certificate Password: {certPassword}");

How This Works:

 In Development: Reads from `user-secrets`.

 In Production: Reads from Azure Key Vault if configured.

 Key Normalization: `.NET` automatically maps 

`Kestrel:Certificates:Development:Password` 

to 

`Kestrel--Certificates--Development--Password` in Key Vault.

How Secrets Work in CI/CD Pipelines

When deploying your application via GitHub Actions or Azure Pipelines, secrets must be injected securely.

GitHub Actions

1. Store a secret in GitHub: Navigate to `Settings → Secrets and variables → Actions` and add a new repository secret.

2. Access the secret in `yaml` workflow:

   - name: Set up environment

     run: echo "Kestrel:Certificates:Development:Password=${{ secrets.CERT_PASSWORD }}" >> $GITHUB_ENV

3. Read the secret in `Program.cs` just like before.

Azure DevOps Pipelines

1. Store a secret in Azure Pipelines (`Library → Secure Files or Variables`).

2. Access it in a YAML pipeline:

    variables:

     - name: CERT_PASSWORD

       value: $(Kestrel-Cert-Password)

Caveats and Best Practices

  • Do not commit secrets to Git – use `gitignore` for `secrets.json`.
  • Use environment-specific configurations – separate development vs. production secrets.
  • Rotate secrets regularly – enforce secret expiration policies.
  • Use managed identities in Azure – instead of storing credentials, use `DefaultAzureCredential` to authenticate.
  • Verify configurations dynamically – print `builder.Configuration.AsEnumerable()` to debug missing keys.

Conclusion

Secrets management is a fundamental part of building secure .NET applications. By using User Secrets for local development and Azure Key Vault for production, you can ensure your application remains secure while keeping environments isolated. Additionally, integrating secrets into CI/CD pipelines ensures smooth deployments without exposing sensitive data.

By following these best practices, you can prevent security leaks and manage credentials safely across different environments. Never store secrets in your codebase—always use secure storage methods!


Comments

Popular posts from this blog

Configuring Any .NET 9.0 Program to Run in Docker: A Step-by-Step Guide

Understand .NET 9.0 Blazor Hosting Models

Understanding a Multi-Stage Dockerfile for .NET 9 Application