Securing Passwords and Keys with UserSecrets

July 17, 2018 0 By Toby Worth

Obviously, it’s not a good idea to keep your sensitive information like passwords, keys and secrets in your code. This will be checked in to shared repos or they can be extracted quite easily if access to the built EXEs/DLLs is obtained. There are several options and while Azure Key Vault is probably the sensible option for many production systems, sometimes we just need a local store so we can get on with developing and not worry about accidentally checking in your SQL admin password1

Keep It Secret, Keep It Safe

So while we’re developing, we can use a secret store in your windows user folder.

Edit the project file and add this element to the packages/tools item group:

[code language=”xml”]
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.2" />
[/code]

Then, add this element to the property group:

[code language=”xml”]
<UserSecretsId>MyApp</UserSecretsId>
[/code]

Save the file and do a ‘dotnet restore’ if using the CLI (this should happen automatically in Visual Studio). This lets you use the CLI to store secrets via the Secret Manager, like so:

[code language=”powershell”]
dotnet user-secrets set SqlAdminPassword "password123"
[/code]

If you run that then check the following path in your user data folder, you should see a new folder with a single file in it called ‘secrets.json’.

[code language=”powershell”]
%USERPROFILE%\AppData\Roaming\Microsoft\UserSecrets\MyApp\
[/code]

Note how the path includes a folder with the name you specified in the project file. The secrets.json file should contain the secret you set with the CLI tool. It’s just an object with properties for each of the key/values you set via the CLI:

[code language=”csharp”]
{
"SqlAdminPassword": "password123"
}
[/code]

So, we have a store for secrets securely in your user data folder (accessible only to you on that machine). This is currently not accessible to the Configuration object in your project, so we have to tell it to use the UserSecrets feature. But first, we’ll define our configuration setup.

For this, we’ll build on the example project from the previous post where EF Core 2.1 is in its own class library.

Entity Framework Core 2.1 in a Class Library

In that post, we set up a simple project to the entity framework entities and context without being part of an API project.  There was no configuration, just a hard-coded connection string, which for remote DB servers would presumably contain a username and password.

Typed Config Objects

A.K.A. The ‘Options Pattern’

Let’s store the configuration values in a strongly-typed object and automate the binding.  I like doing this.  I like pressing ‘.’ and having my config keys appear.  I don’t like ‘magic strings’.

We’ll build a basic object the has the required properties for a remote SQL connection.  Add a new class to the project:

[code language=”csharp”]

namespace MyApp
{
public class DbOptions
{
public string SqlAdminUserId { get; set; }
public string SqlAdminPassword { get; set; }
public string SqlServer { get; set; }
public string InitialCatalog { get; set; }
}
}

[/code]

This example is for an Azure SQL server instance, but you can see the general idea. Create an appsettings.json file and add this to it:

[code language=”csharp”]

{
"ConnectionStrings": {
"AzureDevConnectionTemplate": "Server=tcp:{SqlServer},1433;Initial Catalog={InitialCatalog};Persist Security Info=False;User ID={SqlAdminUserId};Password={SqlAdminPassword};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
}

[/code]

I’ve tokenised the string, so we can parse in the secrets. For this we’ll use SmartFormat.Net, which is ideal for the way string templates *should* work. Either add it via the nuget package manager, or add this line to your project file in the packages/tools item group:

[code language=”powershell”]
<PackageReference Include="smartformat.net" Version="2.3.0" />
[/code]

When this is installed, you can start to bind the user secrets automatically from the

[code]secrets.json[/code]

file into the typed object. We’ll flesh out the DbContextFactory from the previous post with the config mapping and secrets store:

[code language=”csharp”]

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Configuration;
using SmartFormat;
using System.IO;

namespace MyApp
{
public class DbContextFactory : IDesignTimeDbContextFactory<DataContext>
{
private const string userSecretsStoreName = "MyApp";
private IConfiguration Configuration { get; set; }

///

<summary>
/// This is the method that ‘dotnet ef migrations add [Migration_Name]’ will call to get the DbContext.
/// </summary>

/// <param name="args"></param>
/// <returns></returns>
public DataContext CreateDbContext(string[] args)
{
GetConfiguration();

var builder = new DbContextOptionsBuilder<DataContext>();
//builder.EnableSensitiveDataLogging();
builder.UseSqlServer(ParseConnectionString());

return new DataContext(builder.Options);
}

///

<summary>
/// Bind the config data to the typed object
/// and then parse the properties into the connection string template.
/// </summary>

/// <returns></returns>
private string ParseConnectionString()
{
// create DbOptions object to store the secrets in a statically-typed way
var dbOptions = new DbOptions();
// bind the config props to the optionss object
Configuration.GetSection("DbOptions").Bind(dbOptions);

// get the connection string template and parse the options object into it
string conTemplate = Configuration["ConnectionStrings:AzureDevConnectionTemplate"];

return Smart.Format(conTemplate, dbOptions);
}

///

<summary>
/// Build the configuration with User Secrets and the
/// appsettings.json file for the connection string template.
/// </summary>

private void GetConfiguration()
{
var configBuilder = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
<em>.AddJsonFile("appsettings.json",</em> optional: false)
.AddUserSecrets(userSecretsStoreName);

Configuration = configBuilder.Build();
}
}
}
[/code]

From the top, dotnet will call the ‘CreateDbContext’ method when you run

[code]dotnet ef migrations add Initial[/code]

This collates the configuration files, taking the string template and using the type-bound DbOptions settings to parse the values into the relevant parts of the string. That string is pass to the DbContext builder and a new context is built from that and returned from the entry point method.

Now you can run the migration and have it take the secrets from your user data folder! Much more secure and a nice, tidy way to reduce concerns about accidentally sharing your passwords.

Let me know if you spot any errors or have any tips.


1 I NEVER DID THIS.  No, no, no, no, no, no, yes.  Sorry, yep, yes I did.  In fact, I did it twice.  Once with my first set of Azure keys and once a couple of years later with SQL admin.  It was only a personal dev project, but thankfully MS looks for that sort of thing routinely and let me know before someone hijacked my data.  Nevertheless, the shame was so real it still burns.