ASP.NET Core (not that secret) User Secrets Explained

In my previous post, Individual developer settings in ASP.NET Core, I showed you how to create per-developer settings using Windows environment variables. While environment variables are both fast to produce and script friendly, there is a better approach available in ASP.NET Core called User Secrets.

Unlike environment variables, user secrets are placed in a settings file similar to appsettings.json. Having similar structured off-project settings is great when you need to copy keys and values between files and there is support for adding, removing, and listing values as I will show you later in this post.

To understand user secrets, let's resume the example from the previous post. In there I had an appsettings.json file looking like this:

{
  "AppSettings": {
    "ConnectionString": "http://localhost:9000"
  },
  ...
}

In order to override the AppSettings:ConnectionString setting on individual machines, each user needs to add a user secret with the same name. The easiest approach is to right-click the project and select Manage User Secrets:

This creates and opens a new empty JSON file named secrets.json. The file is placed beneath C:\Users\<username>\AppData\Roaming\Microsoft\UserSecrets\<id> where <username> matches your Windows user and <id> is a randomly generated GUID. The important thing to notice here is that the file is located outside your project directory. In order to "bind" the secrets.json file location to your project, Visual Studio added a bit of markup to the csproj file:

<PropertyGroup>
  <UserSecretsId>dda25df4-9a88-4a7e-8502-2134b74e4729</UserSecretsId>
</PropertyGroup>

In case you are not using Visual Studio, you can generate a random GUID and add the <UserSecretsId> manually.

In case you want to override the AppSettings:ConnectionString setting, add a similar structure to the secrets.json file:

{
  "AppSettings": {
    "ConnectionString": "http://localhost:9000?user=richard&password=1234"
  }
}

You can also collapse settings like this:

{
  "AppSettings:ConnectionString": "http://localhost:9000?user=richard&password=1234"
}

I recommend the second approach since you probably only need to override a few settings and since using dotnet from the command line to modify the file, will collapse settings anyway.

Make sure to test that the changes in your secrets.json file work as intended using the application or this Appsettings.json Transformation Tester. The transformation tester won't store anything on our servers, but I still wouldn't recommend you to use files including passwords, production connection strings, and similar in an online tool.

While we are already talking about using dotnet to administrate user secrets, let me show you the three commands you will need to learn:

dotnet user-secrets list

The list command shows you a complete list of keys and values in the secrets.json file.

dotnet user-secrets set "AppSettings:ConnectionString" "some-value"

This command set the AppSettings:ConnectionString key to the specified value.

dotnet user-secrets remove "AppSettings:ConnectionString"

As you already guessed, this command removes the AppSettings:ConnectionString key and value.

Would your users appreciate fewer errors?

➡️ Reduce errors by 90% with elmah.io error logging and uptime monitoring ⬅️

With this knowledge, you are ready to rock and roll. As long as you initialize your web application using the WebHost.CreateDefaultBuilder method, ASP.NET Core automatically picks up your configuration from the secrets.json file. If you initialize your application manually, make sure to call the AddUserSecrets-method:

builder.ConfigureAppConfiguration((hostingContext, config) =>
{
    var env = ctx.HostingEnvironment;
    
    ...
    
    if (env.IsDevelopment())
    {
        var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
        if (appAssembly != null)
        {
            config.AddUserSecrets(appAssembly, optional: true);
        }
    }

    ...
}

Notice how I only add user secrets when running in development mode. User secrets is a feature meant for developing only and should be replaced with something else when hosting on Azure, IIS, or another production environment.

How about environment variables?

Remember environment variables from the last post? The nice thing about user secrets is that they can be used together with environment variables if you like. Settings override in the order they are added to the configuration. If you are using WebHost.CreateDefaultBuilder, environment variables override user secrets which again override settings in appsettings.json. If you want to change the order of override, you will need to configure your app manually and append the config providers in a different order:

builder.ConfigureAppConfiguration((hostingContext, config) =>
{
    ...

    config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)

    // Added before AddUserSecrets to let user secrets override
    // environment variables.
    config.AddEnvironmentVariables();

    // Remember to add this on development only
    config.AddUserSecrets(appAssembly, optional: true);
    
    ...
}

Individual elmah.io logs using user secrets

To make this even more relevant to elmah.io users, let's utilize user secrets to configure individual elmah.io logs per developer machine. For the example, I'm using the Elmah.Io.AspNetCore package, but this could be anything in the need of configuration really.

Start by installing the Elmah.Io.AspNetCore package:

Install-Package Elmah.Io.AspNetCore

Configure the elmah.io options in the ConfigureServices method of the Startup.cs file:

services.Configure<ElmahIoOptions>(Configuration.GetSection("ElmahIo"));

Finally, add elmah.io to the Configure method:

app.UseElmahIo();

So far we haven't done anything outside the normal way of installing elmah.io in ASP.NET Core.

In order for Elmah.Io.AspNetCore to be able to authenticate against the API, add your API key to the appsettings.json file:

{
  ...
  "ElmahIo": {
    "ApiKey": "35a13801bb594a29934a87ea6e9bd13e"
  }
}

Notice how I left out the log ID from the config file? Since we want a log per developer machine, let us add this using a user secret:

dotnet user-secrets set "ElmahIo:LogId" "8e215db7-716e-45ef-8665-c0a9f62a6c0a"

That's it. On runtime, your project will resolve the ElmahIo:ApiKey setting from appsettings.json and the ElmahIo.LogId setting from secrets.json. I used elmah.io as an example here, but the possibilities with this are endless.

Conclusion

To summarize, user secrets are a great alternative to environment variables. The structure matches that of appsettings.json files and there is great tool support for administrating secrets. Popular IDEs like Visual Studio and Code already support the format and there are tools like this JSON formatter and validator to help you write valid JSON. Unlike what the name implies, user secrets are not really a secret. Settings are still stored on the disk in cleartext. People would still need to have access to your Windows account, though. Under all circumstances, user secrets are a great feature in ASP.NET Core and make individual settings easily accessible.