With web.config
going away, what is the preferred way to store sensitive info (passwords, tokens) in the configurations of a web app built using ASP.NET Core?
Is there a way to automatically get encrypted configuration sections in appsettings.json
?
You store configuration data and sensitive network information in configuration files. Encrypting configuration files enables you to secure the information they store. Decrypting means disabling the encryption of configuration files on a device and making the files readable to all.
You can use protected configuration to encrypt sensitive information, including user names and passwords, database connection strings, and encryption keys, in a Web application configuration file such as the Web. config file.
User secrets looks like a good solution for storing passwords, and, generally, application secrets, at least during development.
Check the official Microsoft documentation. You can also review this other SO question.
This is just a way to "hide" your secrets during development process and to avoid disclosing them into the source tree; the Secret Manager tool does not encrypt the stored secrets and should not be treated as a trusted store.
If you want to bring an encrypted appsettings.json
to production, you can do so by building a custom configuration provider.
For example:
public class CustomConfigProvider : ConfigurationProvider, IConfigurationSource { public CustomConfigProvider() { } public override void Load() { Data = UnencryptMyConfiguration(); } private IDictionary<string, string> UnencryptMyConfiguration() { // do whatever you need to do here, for example load the file and unencrypt key by key //Like: var configValues = new Dictionary<string, string> { {"key1", "unencryptedValue1"}, {"key2", "unencryptedValue2"} }; return configValues; } private IDictionary<string, string> CreateAndSaveDefaultValues(IDictionary<string, string> defaultDictionary) { var configValues = new Dictionary<string, string> { {"key1", "encryptedValue1"}, {"key2", "encryptedValue2"} }; return configValues; } public IConfigurationProvider Build(IConfigurationBuilder builder) { return new CustomConfigProvider(); } }
Define a static class for your extension method:
public static class CustomConfigProviderExtensions { public static IConfigurationBuilder AddEncryptedProvider(this IConfigurationBuilder builder) { return builder.Add(new CustomConfigProvider()); } }
And then you can activate it:
// Set up configuration sources. var builder = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .AddEncryptedProvider() .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
I agree with @CoderSteve that writing a whole new provider is too much work. It also doesn't build on the existing standard JSON architecture. Here is a solution that I come up with the builds on top of the standard JSON architecture, uses the preferred .Net Core encryption libraries, and is very DI friendly.
public static class IServiceCollectionExtensions { public static IServiceCollection AddProtectedConfiguration(this IServiceCollection services) { services .AddDataProtection() .PersistKeysToFileSystem(new DirectoryInfo(@"c:\keys")) .ProtectKeysWithDpapi(); return services; } public static IServiceCollection ConfigureProtected<TOptions>(this IServiceCollection services, IConfigurationSection section) where TOptions: class, new() { return services.AddSingleton(provider => { var dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>(); section = new ProtectedConfigurationSection(dataProtectionProvider, section); var options = section.Get<TOptions>(); return Options.Create(options); }); } private class ProtectedConfigurationSection : IConfigurationSection { private readonly IDataProtectionProvider _dataProtectionProvider; private readonly IConfigurationSection _section; private readonly Lazy<IDataProtector> _protector; public ProtectedConfigurationSection( IDataProtectionProvider dataProtectionProvider, IConfigurationSection section) { _dataProtectionProvider = dataProtectionProvider; _section = section; _protector = new Lazy<IDataProtector>(() => dataProtectionProvider.CreateProtector(section.Path)); } public IConfigurationSection GetSection(string key) { return new ProtectedConfigurationSection(_dataProtectionProvider, _section.GetSection(key)); } public IEnumerable<IConfigurationSection> GetChildren() { return _section.GetChildren() .Select(x => new ProtectedConfigurationSection(_dataProtectionProvider, x)); } public IChangeToken GetReloadToken() { return _section.GetReloadToken(); } public string this[string key] { get => GetProtectedValue(_section[key]); set => _section[key] = _protector.Value.Protect(value); } public string Key => _section.Key; public string Path => _section.Path; public string Value { get => GetProtectedValue(_section.Value); set => _section.Value = _protector.Value.Protect(value); } private string GetProtectedValue(string value) { if (value == null) return null; return _protector.Value.Unprotect(value); } } }
Wire up your protected config sections like this:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); // Configure normal config settings services.Configure<MySettings>(Configuration.GetSection("MySettings")); // Configure protected config settings services.AddProtectedConfiguration(); services.ConfigureProtected<MyProtectedSettings>(Configuration.GetSection("MyProtectedSettings")); }
You can easily create encrypted values for your config files using a controller like this:
[Route("encrypt"), HttpGet, HttpPost] public string Encrypt(string section, string value) { var protector = _dataProtectionProvider.CreateProtector(section); return protector.Protect(value); }
Usage: http://localhost/cryptography/encrypt?section=SectionName:KeyName&value=PlainTextValue
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With