Storing production secrets in ASP.NET Core

In addition to use Azure App Service or docker containers, you can also securely store your app secrets in production using IDataProtector:

  1. App secrets are entered with running a -config switch of the application, for example: dotnet helloworld -config; In Program.Main, detects this switch to let user to enter the secrets and store in a separate .json file, encrypted:
public class Program
{
    private const string APP_NAME = "5E71EE95-49BD-40A9-81CD-B1DFD873EEA8";
    private const string SECRET_CONFIG_FILE_NAME = "appsettings.secret.json";

    public static void Main(string[] args)
    {
        if (args != null && args.Length == 1 && args[0].ToLowerInvariant() == "-config")
        {
            ConfigAppSettingsSecret();
            return;
        }
        CreateWebHostBuilder(args).Build().Run();
    }

    public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .ConfigureAppConfiguration((builder, options) =>
            {
                options.AddJsonFile(ConfigFileFullPath, optional: true, reloadOnChange: false);
            })
            .UseStartup<Startup>();

    internal static IDataProtector GetDataProtector()
    {
        var serviceCollection = new ServiceCollection();
            
        serviceCollection.AddDataProtection()
            .SetApplicationName(APP_ID)
            .PersistKeysToFileSystem(new DirectoryInfo(SecretsDirectory));
        var services = serviceCollection.BuildServiceProvider();
        var dataProtectionProvider = services.GetService<IDataProtectionProvider>();
        return dataProtectionProvider.CreateProtector(APP_ID);
    }

    private static void ConfigAppSettingsSecret()
    {
        var protector = GetDataProtector();

        string dbPassword = protector.Protect("DbPassword", ReadPasswordFromConsole());
        ... // other secrets
        string json = ...;  // Serialize encrypted secrets to JSON
        var path = ConfigFileFullPath;
        File.WriteAllText(path, json);
        Console.WriteLine($"Writing app settings secret to '${path}' completed successfully.");
    }

    private static string CurrentDirectory
    {
        get { return Directory.GetParent(typeof(Program).Assembly.Location).FullName; }
    }

    private static string ConfigFileFullPath
    {
        get { return Path.Combine(CurrentDirectory, SECRET_CONFIG_FILE_NAME); }
    }
}
  1. In Startup.cs, read and decrypt the secret:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    ...
    if (env.IsProduction())
    {
        var protector = Program.GetDataProtector();
        var builder = new SqlConnectionStringBuilder();
        builder.Password = protector.Unprotect(configuration["DbPassword"]);
        ...
    }
}

BTW, appsettings.production.json or environment variable is really not an option. Secrets, as its name suggests, should never be stored in plain text.

Leave a Comment

Hata!: SQLSTATE[HY000] [1045] Access denied for user 'divattrend_liink'@'localhost' (using password: YES)