User Secrets in a .NET Core Web App

May 18, 2018
Written by

7XMCQ6zhfvbbT4Hf-XxrjbKM8GjV9IZIax0zJmkvtaTSXVfi7gDusJepXufpfu4j0R2BDNNvMaOgzcV_CtQcwWYNUip440zCvQ5yJ0-8sFVkZApwLdjtyRA4YSaVpouZFq6M4-s8-2

Ever had that sinking moment of realisation when you push your secrets to GitHub?  I have and I doubt I’m the only one.

There are many reasons why you wouldn’t want your sensitive configurations shared and I’m not just talking about on GitHub.  Members of a development team may not use the same test databases or connection strings.  Maybe the dev team only has access to the test keys for apps such as Twitter but the live keys are squirrelled away in Azure.

A common way to deal with sensitive data in an app is by using Environment Variables.  With the arrival of .NET Core we now have a tidy way of managing configuration and sensitive data in the form of User Secrets, which can be managed by the Secrets Manager Tool (SMT) from the command line.  User Secrets are stored outside of the project tree in a JSON configuration file in the user profile directory and are therefore outside of source control.

See this post to add User Secrets in a console app.

I have created a solution on GitHub so feel free to follow along with the completed project or have a go at implementing it into your own .NET Core API or MVC web application with your own secrets.

Adding User Secrets to your project

On Windows with Visual Studio 2017

If you are using Visual Studio on Windows and have a web project, then adding User Secrets takes only a few clicks.  Right click on the project and select  Manage User Secrets.

Clicking this will do a couple of things; it will open the secrets.json file in Visual Studio where you can add your secrets and it will add the  <userSecretsId> to the .csproj file.

It’s also a good idea to add the SMT to the .csproj file which will enable you to update the User Secrets from the command line.

  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
  </ItemGroup>

Update: As of .NET Core 2.1, the SMT will be included on the dotnet cli, so you won’t need to add it separately.

Below is an example of what you may wish to add to your User Secrets. It includes two ways of writing a json object and a simple key/value pair.  Not everything needs to be secret so don’t get carried away.  I would normally store anything that I wouldn’t want in a public repo in the secrets.json file.

{
  "TwilioAccountDetails": {
    "AccountSid": ACCOUNT_SID,
    "AuthToken": AUTH_TOKEN
  },

  "SecretStuff:SecretTwo": "In Welsh lore, Fairies rode Corgis into battle",
  "SecretStuff:SecretOne": "The lint that collects in the bottom of your pockets has a name — gnurr",

  "Key": "Value" 
}

SMT will handle the storage of your secrets and place them in %APPDATA%\Microsoft\UserSecrets\<guid>\secrets.json without you really having to worry about the implementation.

On Linux, macOS or alternative IDE

If you are not using Visual Studio 2017 or you are working in Linux or macOS, the option to Manage User Secrets will not be available to you via a right-click.  In this case you will need to add the secrets manually.

As I mentioned, User Secrets are stored outside of your project tree and on Linux and macOs are stored in ~/.microsoft/usersecrets/<guid>/secrets.json.

If the .microsoft folder is not present on Linux or macOS, then you’ll need to create it.  Remember it may be hidden, so check for that before creating a new file. You can list hidden as well as non hidden files with the ls -la command in your terminal. If you do need to create a new .microsoft folder you can do so by running the following command in your terminal:

mkdir ~/.microsoft

You will need to create the <userSecretsId> GUID and can do so using a GUID generator such as this one.

Add the tag to the .csproj underneath the <TargetFramework> tag and the SMT in an <ItemGroup>:


  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <UserSecretsId>USER_SECRETS_GUID</UserSecretsId>
  </PropertyGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
  </ItemGroup>

Now we can create and update the User Secrets from the command line, this goes for any project type in any OS.  To do this, navigate to the root of the project, where the .csproj file is, and enter the following command:

dotnet user-secrets set TwilioAccountDetails:AccountSid ACCOUNT_SID

If you already have a value stored in your environment variables, you can replace ACCOUNT_SID with the name of your environment variable prefixed with $ to read it and save it to the User Secrets. For example $TWILIO_ACCOUNT_SID.

User Secrets can be shared across multiple projects via their GUID or even a different set of User Secrets for each branch of your solution, making maintenance of shared data that little bit easier.

It is worth noting that User Secrets are not encrypted and are only available during development.

Mapping User Secrets to a model

The best way to consume User Secrets, in my opinion, is to map them to a model.  This will give type safety and prevent any errors from misspelt string names.  It also makes your code that little bit more testable.

There are a few steps to do this, the first being to add the secrets to the appsettings.json, found in the root of your project.  This will need to correlate closely to your secrets.json file, only with sensitive data removed.  I usually add a note to myself about where I have actually set my sensitive data as in the following example:


{
 "Logging": {
   "IncludeScopes": false,
   "Debug": {
     "LogLevel": {
       "Default": "Warning"
     }
   },
   "Console": {
     "LogLevel": {
       "Default": "Warning"
     }
   }
 },
 "TwilioAccountDetails": {
   "AccountSid": "Set in Azure. For development, set in User Secrets",
   "AuthToken": "Set in Azure. For development, set in User Secrets"
 }
}

Next we need to create the C# model that we want to map the secrets to.  To keep my solution organised I tend to add a Configuration folder to my Models folder whether that be in the same project or in a dedicated models project being referenced.  Make sure that the names in the secrets.json file are the same in both the appsettings.json file and the C# models.

I will repeat this process for each json object or key/value pair I have created in my secrets.json.

public class TwilioAccountDetails
{
   public string AccountSid { get; set; }
   public string AuthToken { get; set; }
}

 

Adding the Configuration

We will need to install a few nuget packages to the .csproj of our web app.


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

  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <UserSecretsId>USER_SECRETS_GUID</UserSecretsId>
  </PropertyGroup>

  <ItemGroup>
    <Folder Include="wwwroot\" />
  </ItemGroup>
   <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.8" />
    <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.0.4" />
    <PackageReference Include="Microsoft.Extensions.Configuration" Version="2.0.2" />
    <PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="2.0.2" />
    <PackageReference Include="Twilio.AspNet.Core" Version="5.9.7" />
  </ItemGroup>
  <ItemGroup>
    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
    <DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="2.0.0" />
  </ItemGroup>
</Project>

Restore your nuget packages from within Visual Studio, by right clicking on the solution and selecting Restore NuGet Packages, or from the command line by using dotnet restore. Now that we have the models, we can bind the values. This is done in the Startup.cs file.


namespace WebApp
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {

            // -------------- Configuration ---------------
            services.Configure<TwilioAccountDetails>(Configuration.GetSection("TwilioAccountDetails"));

            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseMvc();
        }
    }
}

 

Using your mapped secrets

Now that our secrets have been mapped, we can use .NET Core’s built in dependency injection to inject the secrets into the constructors of classes and services.  This is done via the IOptions<> interface.  For more information on Dependency Injection in .NET Core have a look at this post from Microsoft.


 public class MessageController : Controller
    {
        private readonly TwilioAccountDetails _twilioAccountDetails;
        // I’ve injected twilioAccountDetails into the constructor

        public MessageController(IOptions<TwilioAccountDetails> twilioAccountDetails)
        {
             // We want to know if twilioAccountDetails is null so we throw an exception if it is           
             _twilioAccountDetails = twilioAccountDetails.Value ?? throw new ArgumentException(nameof(twilioAccountDetails));
        }

        [HttpGet]
        public IActionResult Get()
        {
            return Content($"My Account Sid is {_twilioAccountDetails.AccountSid}");
        }
...

 

Overwriting AppSettings on Azure

If you’re deploying a web app to Azure, it’s relatively straightforward to add your secrets, requiring only a single step.  From within the portal, select your web app and then scroll down the menu until you find application settings.
Then enter your secrets, matching case, as key/value pairs.

Summary

That’s it! You can now extract your configuration and sensitive data from your code.  This affords you and your team more flexibility, security and testability.
Remember that User Secrets are not encrypted and can only be used within the development environment.

If you want to review the code in its entirety checkout out the GitHub repo, where both the WebApp and the Console App can be found.   Drop a comment below or tweet me at @LaylaCodesIt if you have any questions. I can’t wait to see what you build!