Secure Development in .NET: An Introduction to UserSecrets

Imagine you have an appsetting.json file that contains sensitive information such as your database server password, secret keys, and hash values for hashing.

{
 "Logging": {
   "LogLevel": {
     "Default": "Information",
     "Microsoft.AspNetCore": "Warning"
   }
 },
 "AllowedHosts": "*",
 
 "ConnectionStrings": {
   "MyDBContext": "Server=.;Database=SecretDemo;user id=sa;password=abCd@@123; TrustServerCertificate=True;"
 
 },
 "Credentials": {
   "Algorithm": "SHA256",
   "SecretKey": "ABCD1234",
   "Salt": "abcd!@#$"
 
 },
 "IPAdress": "192.168.0.1"
 
}

To safeguard this confidential information, consider using Microsoft.Extensions.Configuration.UserSecrets. This tool enables proper secret management in your projects. First, install it from the NuGet packages. Then, right-click your project and select ‘Manage User Secrets’. This will create a secret.json file.

Next, move the sensitive details from your appsetting.json to the secret.json file. In my case, the file looks like this:

{
 
  "ConnectionStrings": {
    "MyDBContext": "Server=.;Database=SecretDemo;user id=sa;password=abCd@@123; TrustServerCertificate=True;"
 
  },
  "Credentials": {
    "SecretKey": "ABCD1234",
    "Salt": "abcd!@#$"
 
  }
 
}

And my appsetting.json will look like this:

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
 
  "Credentials": {
    "Algorithm": "SHA256"
 
  },
  "IPAdress": "192.168.0.1"
}

Here, I’ve transferred what I consider confidential information to secret.json. For instance, within the configuration object “Credentials”, I have three properties: “SecretKey”, “Salt”, and “Algorithm”. Believing the first two to be sensitive, I moved them to secret.json, leaving “Algorithm” in the config file as it’s not sensitive.

To show it works under secret.json, I will print those values out in html page. The backend and front end code are like the following:

public IndexModel(ILogger<IndexModel> logger, IConfiguration configuration)
{
    _logger = logger;
    _configuration = configuration;
}

public void OnGet()
{
     
    MyDBContext = _configuration["ConnectionStrings:MyDBContext"];
    SecretKey = _configuration["Credentials:SecretKey"];
    Salt = _configuration["Credentials:Salt"];
//...omitted code
<div>
    <p>The value of MyDBContext is: @Model.MyDBContext </p>
    <p>The value of SecretKey is: @Model.SecretKey </p>
    <p>The value of Salt is: @Model.Salt </p>
</div>

The output will look like this, displaying all values even though some are in secret.json

Secure Development in .NET

If you deploy your web app to IIS, you’ll need to manage your secrets differently, as your secret.json file won’t be deployed. You must set your secrets directly in IIS.

In IIS, go to the ‘Features View’ and double-click “Configuration Editors”. Under the section system.webServer/aspNetCore, you will see the following options:

Secure Development in .NET

Select the environmentVariables, and click the ‘3 dots’ button on the right to add. You can then add your secret variable.

Secure Development in .NET

Follow this naming convention for your variables: [object][underscore][underscore][property]. For instance, if the object name for the connection string is “ConnectionStrings”, and the property name is “MyDBContext”, the environment variable should be “ConnectionStrings__MyDBContext”. Remember to use TWO underscores; using only one gave me a massive headache during my initial environment variable setup. Once you’re done, close the window and click “Apply” to save your changes in IIS.

After launching the website, you’ll notice that the app now captures and stores your secret information.

If you do not want to set the variables individually in IIS, you can use the Powershell command to load it. First, save your secret.Json file to one path (for my case, I save it to C:\SecretPath), and execute the following command:

$json = Get-Content 'C:\SecretPath\secret.json' | ConvertFrom-Json
foreach($parentProp in $json.PSObject.Properties){
    foreach($childProp in $parentProp.Value.PSObject.Properties){
        $envVarName = $parentProp.Name + '__' + $childProp.Name
       [System.Environment]::SetEnvironmentVariable($envVarName, $childProp.Value, 'Machine')
    }
}
 
 
# This command is for if your secret object do not have nested properties 
# $json = Get-Content 'C:\SecretPath\secret.json' | ConvertFrom-Json
# foreach($prop in $json.PSObject.Properties){
#     [System.Environment]::SetEnvironmentVariable($prop.Name, $prop.Value, 'Machine')
# }

After running the command, execute Get-ChildItem Env: to list all environment variables in your machine. As you can see below, my credentials have been added.

Secure Development in .NET

And if you use this method, the source code has to be updated to the following to get the environment variables.

MyDBContext = Environment.GetEnvironmentVariable("ConnectionStrings__MyDBContext", EnvironmentVariableTarget.Machine);

SecretKey = Environment.GetEnvironmentVariable("Credentials__SecretKey", EnvironmentVariableTarget.Machine);

Salt = Environment.GetEnvironmentVariable("Credentials__Salt", EnvironmentVariableTarget.Machine);

If you want to update the value of the credentials, you change the values of your secret.json, and repeat the above Powershell command to load the secret.json again.

If you want to delete the environment variables, you can execute the following Powershell command to delete it.

[System.Environment]::SetEnvironmentVariable(“YourEnvironmentVariable”, $null, ‘Machine’)

For example, I wanted to delete my SecretKey properties from Credentials, so my Powershell command will be like this:

[System.Environment]::SetEnvironmentVariable("Credentials__SecretKey", $null, 'Machine')

And my website will look like this, with the Secret Key becoming null.

Secure Development in .NET

In conclusion, Microsoft.Extensions.Configuration.UserSecrets is an excellent tool for managing secrets. It’s especially beneficial when working in a team, as storing sensitive information outside the config file can prevent inadvertent data exposure during code check-ins.