Skip to content

Safe storage of application secrets in PSS®X development

Overview

Working with secure keys in ASP.NET Core has always been a delicate balance of convenience and safety. It’s easy to add a production database connection string to your application’s configuration for quick testing, only to accidentally check it into source control.

Danger

Never store passwords or other sensitive data in source code.

Production secrets shouldn't be used for development or test. Secrets shouldn't be deployed with the app. Instead, production secrets should be accessed through a controlled means like environment variables, secret providers etc.

In an ASP.NET Core application, configuration settings are managed using key-value pairs provided by configuration providers. Configuration providers read configuration data into key-value pairs from a variety of configuration source like settings files, environment variables, etc. The appsettings.json file, which is the default configuration file, contains various settings for your application.

This is how a configuration file is added to an application:

...
builder.Host
   .ConfigureAppConfiguration((context, config) =>
   {
      config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); # File based
      config.AddEnvironmentVariables(); # Environment value based
   };
...

and this is how a configuration may look like for a connection string looks like:

"ConnectionStrings": {
  "Default": "pssx-sql-server;User Id=sa;Password=myPassword;Initial Catalog=pssx-app;MultipleActiveResultSets=true;TrustServerCertificate=True",
  "Hangfire": "pssx-sql-server;User Id=sa;Password=myPassword;Initial Catalog=pssx-hangfire;MultipleActiveResultSets=true;TrustServerCertificate=True"
}

In the above example the password is stored in plain text and unencrypted and this is not safe.

Secret Manager

.NET SDK has a secrets manager, a command-line utility that uses your current project to create a unique store for project-related secrets. When initialized, .NET will store your project secrets in a user-specific directory outside the current project. Depending on your host operating system, these folders differ on Windows and Linux/macOS.

File system path:

%APPDATA%\Microsoft\UserSecrets\<user_secrets_id>\secrets.json

File system path:

~/.microsoft/usersecrets/<user_secrets_id>/secrets.json

In the preceding file paths, replace <user_secrets_id> with the UserSecretsId value specified in the project file.

Don't write code that depends on the location or format of data saved with the Secret Manager tool. These implementation details may change.

Enable secret storage

The Secret Manager tool operates on project-specific configuration settings stored in your user profile.

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <UserSecretsId>$(MSBuildProjectName)</UserSecretsId>
  </PropertyGroup>

</Project>

Use the CLI

You can also run the following command from terminal in the project directory.

dotnet user-secrets init --project src/GridLab.<ApplicationName>.AuthServer --id GridLab.<ApplicationName>.AuthServer
dotnet user-secrets init --project src/GridLab.<ApplicationName>.HttpApi.Host --id GridLab.<ApplicationName>.HttpApi.Host
dotnet user-secrets init --project src/GridLab.<ApplicationName>.Web --id GridLab.<ApplicationName>.Web
dotnet user-secrets init --project src/GridLab.<ApplicationName>.Web.Public --id GridLab.<ApplicationName>.Web.Public

Use Visual Studio

In Visual Studio, right-click the project in Solution Explorer, and select Manage User Secrets from the context menu.

This gesture adds a UserSecretsId element, populated with a GUID, to the project file if you didn't set property before.

Register the user secrets configuration source

The user secrets configuration provider registers the appropriate configuration source with the .NET Configuration API.

var builder = WebApplication.CreateBuilder(args);
builder.Host
    .AddAppSettingsSecretsJson()
    .UseAutofac()
    .UseSerilog();
await builder.AddApplicationAsync<PSSXAuthServerModule>();
var app = builder.Build();

WebApplication.CreateBuilder initializes a new instance of the WebApplicationBuilder class with preconfigured defaults. The initialized WebApplicationBuilder (builder) provides default configuration and calls AddUserSecrets when the EnvironmentName is Development.

Set a secret

Define an app secret consisting of a key and its value. The secret is associated with the project's UserSecretsId value.

For example, run the following command from the directory in which the project file exists:

dotnet user-secrets set "Swagger:ClientSecret" "1q2w3e*" --project src/GridLab.<ApplicationName>.HttpApi.Host

JSON structure flattening in Visual Studio

Visual Studio's Manage User Secrets gesture opens a secrets.json file in the text editor. Replace the contents of secrets.json with the key-value pairs to be stored. For example:

{
  "Swagger": {
    "ClientId": "PSSX_Swagger",
    "ClientSecret": "1q2w3e*"
  }
}

The JSON structure is flattened after modifications via dotnet user-secrets remove or dotnet user-secrets set. For example, running dotnet user-secrets remove "Swagger:ClientId" collapses the Swagger object literal.

The modified file resembles the following JSON:

{
  "Swagger:ClientSecret": "1q2w3e*"
}

Set multiple secrets

A batch of secrets can be set by piping JSON to the set command. In the following example, the input.json file's contents are piped to the set command.

Open a command shell, and execute the following command:

type .\input.json | dotnet user-secrets set

Open a command shell, and execute the following command:

cat ./input.json | dotnet user-secrets set