Skip to content

Secure Configuration in .NET Applications

This guide covers best practices for managing secrets and configuration in .NET applications using environment variables and various secret management solutions.

1. Introduction to Configuration Security

Sensitive information such as connection strings, API keys, and credentials should never be stored in:

  • Source code repositories
  • Configuration files committed to source control
  • Plaintext on servers

2. Using Environment Variables

Environment variables provide a simple way to manage configuration without hardcoding sensitive data.

Setting environment variables in Ubuntu

bash
# Setting temporarily
export ConnectionString="Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;"

# Setting permanently in .profile or .bashrc
echo 'export ApiKey="your-secret-api-key"' >> ~/.bashrc
source ~/.bashrc

Reading environment variables in .NET

csharp
// Program.cs
var connectionString = Environment.GetEnvironmentVariable("ConnectionString");
var apiKey = Environment.GetEnvironmentVariable("ApiKey");

In ASP.NET Core

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Add environment variables as a configuration source
builder.Configuration.AddEnvironmentVariables();

3. Azure Key Vault Strategy

Azure Key Vault provides a secure service for storing keys, secrets, and certificates.

Setup

  1. Install required NuGet packages:
bash
dotnet add package Azure.Identity
dotnet add package Azure.Security.KeyVault.Secrets
dotnet add package Microsoft.Extensions.Configuration.AzureKeyVault

# Add reference project 
dotnet add reference ../MyProject

# remove reference project
dotnet remove reference ../MyProject

APP_USER=myusername APP_PASSWORD=mypassword docker-compose up
  1. Create an Azure Key Vault and add secrets

Implementation

csharp
// Program.cs in ASP.NET Core application
var builder = WebApplication.CreateBuilder(args);

// Add Azure Key Vault configuration
string keyVaultUrl = builder.Configuration["KeyVaultSettings:VaultUri"];
string tenantId = builder.Configuration["KeyVaultSettings:TenantId"];
string clientId = builder.Configuration["KeyVaultSettings:ClientId"];
string clientSecret = builder.Configuration["KeyVaultSettings:ClientSecret"];

builder.Configuration.AddAzureKeyVault(
    new Uri(keyVaultUrl),
    new ClientSecretCredential(tenantId, clientId, clientSecret));

// Now you can access secrets as if they were configuration settings
// var mySecret = builder.Configuration["MySecretName"];

var app = builder.Build();
// ...rest of application setup

Usage Example

csharp
// WeatherForecastController.cs
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private readonly IConfiguration _configuration;

    public WeatherForecastController(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    [HttpGet]
    public IActionResult Get()
    {
        // Access the secret from Key Vault
        var apiKey = _configuration["ExternalApiKey"];
        
        // Use the apiKey to make external API calls
        // ...

        return Ok(new { message = "Data retrieved successfully" });
    }
}

4. HashiCorp Vault Strategy

HashiCorp Vault is a powerful secrets management tool that works well in multi-platform environments.

Setup

  1. Install required packages:
bash
dotnet add package VaultSharp

Implementation

csharp
// VaultService.cs
public class VaultService
{
    private readonly IVaultClient _vaultClient;
    
    public VaultService(string vaultAddress, string vaultToken)
    {
        var vaultClientSettings = new VaultClientSettings(
            vaultAddress,
            new TokenAuthMethodInfo(vaultToken)
        );
        
        _vaultClient = new VaultClient(vaultClientSettings);
    }
    
    public async Task<string> GetSecretAsync(string path, string key)
    {
        var secret = await _vaultClient.V1.Secrets.KeyValue.V2.ReadSecretAsync(path);
        return secret.Data.Data[key].ToString();
    }
}

Registering as a Service

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Get Vault configuration from environment variables
string vaultAddress = builder.Configuration["Vault:Address"];
string vaultToken = builder.Configuration["Vault:Token"];

// Register VaultService
builder.Services.AddSingleton<VaultService>(sp => 
    new VaultService(vaultAddress, vaultToken));

var app = builder.Build();
// ...rest of application setup

Usage Example

csharp
// PaymentController.cs
[ApiController]
[Route("[controller]")]
public class PaymentController : ControllerBase
{
    private readonly VaultService _vaultService;

    public PaymentController(VaultService vaultService)
    {
        _vaultService = vaultService;
    }

    [HttpPost]
    public async Task<IActionResult> ProcessPayment()
    {
        // Get payment gateway API key from Vault
        var apiKey = await _vaultService.GetSecretAsync("payment/api-keys", "stripe");
        
        // Use the API key for payment processing
        // ...
        
        return Ok(new { message = "Payment processed" });
    }
}

5. Docker Secrets Strategy

Docker Secrets is ideal for containerized applications, especially in Swarm mode.

Creating Docker Secrets

bash
# Create a secret
echo "my-database-password" | docker secret create db_password -

# Creating from a file
docker secret create cert_file ./server.cert

Docker Compose Configuration

yaml
# docker-compose.yml
version: '3.8'

services:
  api:
    image: my-dotnet-api
    secrets:
      - db_password
      - api_key
    environment:
      - DB_PASSWORD_FILE=/run/secrets/db_password
      - API_KEY_FILE=/run/secrets/api_key

secrets:
  db_password:
    external: true
  api_key:
    external: true

Implementation in .NET

csharp
// SecretReader.cs
public static class SecretReader
{
    public static string GetSecret(string secretFile)
    {
        try
        {
            return File.ReadAllText(secretFile).Trim();
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Error reading secret file: {ex.Message}");
            return null;
        }
    }
}

Usage Example

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Get paths to secret files from environment variables
string dbPasswordFile = Environment.GetEnvironmentVariable("DB_PASSWORD_FILE");
string apiKeyFile = Environment.GetEnvironmentVariable("API_KEY_FILE");

// Read secrets
string dbPassword = SecretReader.GetSecret(dbPasswordFile);
string apiKey = SecretReader.GetSecret(apiKeyFile);

// Configure services using the secrets
builder.Services.AddDbContext<ApplicationDbContext>(options =>
{
    var connectionString = $"Server=db;Database=myapp;User=admin;Password={dbPassword};";
    options.UseSqlServer(connectionString);
});

// Register API client with key
builder.Services.AddHttpClient("externalApi", client =>
{
    client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
});

var app = builder.Build();
// ...rest of application setup

6. Hybrid Approach for Different Environments

A common strategy is to use different methods based on the environment:

csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

// Base configuration from appsettings.json
var configuration = builder.Configuration;

// Add environment variables
configuration.AddEnvironmentVariables();

// Add appropriate secret manager based on environment
if (builder.Environment.IsProduction())
{
    // In production: Use Azure Key Vault or HashiCorp Vault
    string keyVaultUrl = configuration["KeyVault:Url"];
    // Add Key Vault configuration
    // ...
}
else if (builder.Environment.IsStaging())
{
    // In staging: Docker Secrets or HashiCorp Vault
    // ...
}
else
{
    // In development: Use user secrets
    configuration.AddUserSecrets<Program>();
}

var app = builder.Build();
// ...rest of application setup

7. Best Practices

  1. Rotate secrets regularly - Implement a process to rotate credentials

  2. Use managed identities when possible - Especially with Azure services

  3. Implement secret caching - Avoid unnecessary calls to the vault service:

csharp
// CachingVaultService.cs
public class CachingVaultService
{
    private readonly VaultService _vaultService;
    private readonly IMemoryCache _cache;
    private readonly TimeSpan _cacheExpiration = TimeSpan.FromMinutes(15);

    public CachingVaultService(VaultService vaultService, IMemoryCache cache)
    {
        _vaultService = vaultService;
        _cache = cache;
    }

    public async Task<string> GetSecretAsync(string path, string key)
    {
        string cacheKey = $"{path}:{key}";
        
        if (!_cache.TryGetValue(cacheKey, out string cachedSecret))
        {
            cachedSecret = await _vaultService.GetSecretAsync(path, key);
            
            _cache.Set(cacheKey, cachedSecret, _cacheExpiration);
        }
        
        return cachedSecret;
    }
}
  1. Implement fallback mechanisms - Have contingency plans for when vault services are unavailable

  2. Audit secret access - Implement logging and monitoring for secret access patterns

8. Comparing Strategies

StrategyProsConsBest For
Environment VariablesSimple, universalNo encryption at rest, no versioningDevelopment, small applications
Azure Key VaultManaged service, high availability, integrates with Azure servicesAzure-specific, potential costAzure-hosted applications
HashiCorp VaultPlatform-agnostic, advanced featuresComplex setup, self-managedMulti-cloud, enterprise applications
Docker SecretsSimple for containerized apps, native Docker integrationLimited to Docker environmentDocker Swarm deployments

9. Additional Resources