Storing Secrets Locally with Secret Manager and ASP.NET Core

Dec 14, 2023

Overview

When working locally in .NET we often have very private and confidential settings and configuration that we don't want leaking into our source control or easily exposed to malicious actors. These can be things like database connection strings, API keys, passwords etc

We need a way when developing locally to securely hide these secrets and configuration from prying eyes. This is where the Secret Manager comes in.

What is the Secret Manager?

Secret Manager allows us to store secrets and configuration during development in a secure location on your machine and is then used by your application using existing .NET configuration source providers.

If we are running an ASP.NET application we can get settings and configuration from different location, with appsettings being a primary place but also the environment. Neither of these is secure when developing locally.

appsettings.json files are checked into source control by default and the environment is also vulnerable.

When then deploying to production we can use things like Azure Key Vault to secure our secrets but working locally we need a way to store these secrets securely.

Setup

We can use the dotnet CLI in a terminal window to setup the Secret Manager in our project. First we need to navigate to the root of our project and then run the following command:

dotnet user-secrets init

This does a few things:-

  1. This creates an entry in your .csproj file that looks like this with a UserSecretsId Guid value

  2. In a secure part of your machine the Secret Manager creates a folder with the same Guid value to store your secrets in a secrets.json file. Depending on whether you are running on a Mac or Windows machine the location will vary.

  • On Linux / Mac it will be stored in the following location:
~/.microsoft/usersecrets/<user_secrets_id>/secrets.json
  • On Windows it will be stored in the following location:
%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json

Here is an example of what the folder structure looks like on my Mac with the different user secrets folders from different applications. There is a different GUID for each project that uses the Secret Manager:

On opening one of those folders you see the secrets.json file which is a json file of key / value pairs containing the secrets for that project:

There are different ways to maintain secrets in this file.

You can either edit the file directly, use the dotnet CLI or use tooling in your IDE to manage the secrets.

  1. We can now use the DOTNET CLI if we want to manage our keys. For example, if we want to add a new key called MySecret we can run the following command:
dotnet user-secrets set "MySecret" "123abc"

This will list our secrets:

dotnet user-secrets list
  1. Or here using the Rider Tooling edit the secrets.json file directly:

which opens up an editor windows where we can maintain our secrets:

How do we use the secrets in our application?

.NET has built in configuration sources that will look for configuration in different places (sources). The Secret Manager is one such configuration source that gets added to the list of configuration sources when we initialise secrets in our application and when the application runs.

In this example below using .NET 8 ASP.NET Core app using minimal API we can see that before we have initialised the Secret Manager when we look at builder.Configuration.Sources we will see the following sources that .NET is using to get configuration from the application.

Couple of things to note above:

  1. The order that the configuration is loaded is important. The last source in the list will override any values that are in the previous sources.
  2. We don't have anything looking at secrets.json yet so we need to add that.

When we look at 4 and 5 (JsonConfigurationSource entries) we can see these are the configuration sources for reading appsettings.json and appsettings.Development.json files.

After initialising our secrets we see an extra JsonConfigurationSource is added to the Configuration Sources list (this is item 6 in the list below):

And we can see on inspecting this that the secrets.json file is now being used to read configuration from:

In our .NET application we can then read our secret as we do with any configuration and this will then read our secret from the secrets.json file using this additional configuration source that overrides existing configuration sources.

var secret = builder.Configuration["MySecret"] ?? "No Secret";

Conclusion

This keeps all our secrets secure when developing locally and different IDEs have tooling to help us manage our secrets or you can use the dotnet CLI.

The order of the configuration providers is interesting as the values in appsettings are overwritten by the secrets configuration source but if we were to remove our secrets the settings in our appsettings would be used.

In production the local secrets file would not exist and you would fallback to your Production secure configuration (Key Vault if running on Azure) but for working locally this is the a good practice to keep key configuration out of source control.