ASP.NET and the Secret Store

I’ve lost count of the number of times I’ve checked in something I shouldn’t have. The ClientSecret to my Auth0 app configuration, an embedded Administrator password, or worse. It’s all gone into Git and then been synced to GitHub. I’ve mentioned numerous times to ensure you add some JSON file or other to .gitignore. I was rather pleased when ASP.NET5 beta5 came out. It had a solution to my problem in the form of UserSecrets. In this tutorial, I’m going nuts to bolts on user secrets.

Step 1: Install DNVM

The Getting Started with ASP.NET 5 and DNX page suggests that the “latest preview of Visual Studio 2015” installs dnvm. Well, not for me. I had to install DNVM on my own. TO do this, I opened up a PowerShell prompt and did the following:

&{$Branch='dev';iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/aspnet/Home/dev/dnvminstall.ps1'))}

This says it is adding things to your path. It didn’t. I added the resulting path – C:\Users\adrian\.dnx\bin – to my PATH in my profile.ps1 file. Specifically, I added the following:

Add-Path "$(${env:HOME})\.dnx\bin"

You can find out about my PathUtils module elsewhere on my blog.

Once I’d got dnvm installed, I could download the latest version of DNX:

dnvm upgrade

Step 2: Install SecretManager

Once you have dnx installed, you can install the SecretManager:

dnu commands install SecretManager

This will allow you to run a command called user-secret:

user-secret --help

If you get this far, you are all set.

Step 3: Create an ASP.NET5 Application

I’m going to create a simple ASP.NET5 WebAPI application for the purposes of demonstration. It will have one route – /api/settings – that will output the information I need to configure Auth0 in the browser. The idea is that my browser application will download this config as a JSON document and then use the information in there to configure the Auth0 authentication. To test it, I’m going to use Postman which is a plug-in for Google Chrome. Let’s start with an Empty ASP.NET5 application and do a quick tour of the code. First off, the project.json file:

{
    "webroot": "wwwroot",
    "version": "1.0.0-*",

    "dependencies": {
        "Microsoft.AspNet.Mvc": "6.0.0-beta5",
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta5",
        "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5",
        "Microsoft.Framework.Configuration.Json": "1.0.0-beta5"
    },

    .....
}

I haven’t reproduced the entire file – just the bits that matter – the dependencies. Just two new packages are absolutely required here. Now the Startup.cs file:

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Framework.Configuration;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Runtime;

using UserSecretWeb.Settings;

namespace UserSecretWeb
{
    public class Startup
    {
        public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
        {
            var configBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath);
            configBuilder.AddJsonFile("config.json", optional: true);
            configBuilder.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
            configBuilder.AddEnvironmentVariables();

            Configuration = configBuilder.Build();
        }

        public IConfiguration Configuration
        {
            get;
            private set;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<Auth0Settings>(this.Configuration.GetConfigurationSection("Auth0"));

            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app)
        {
            app.UseMvc();
        }
    }
}

This comes directly from my Configuration and Dependency Injection article. I’ve got an Settings/Auth0Settings class to hold my configuration – it’s basically a model:

namespace UserSecretWeb.Settings
{
    public class Auth0Settings
    {
        public string Domain
        {
            get;
            set;
        }

        public string ClientID
        {
            get;
            set;
        }

        public string ClientSecret
        {
            get;
            set;
        }
    }
}

I also have a controller to expose the /api/settings WebAPI – that’s in Controllers/SettingsController.cs:

using Microsoft.AspNet.Mvc;
using Microsoft.Framework.OptionsModel;
using UserSecretWeb.Settings;

namespace UserSecretWeb.Controllers
{
    [Route("api/[controller]")]
    public class SettingsController : Controller
    {
        private Auth0Settings auth0Settings = null;

        public SettingsController(IOptions<Auth0Settings> settings)
        {
            this.auth0Settings = settings.Options;
        }

        // GET: api/settings
        [HttpGet]
        public Auth0Settings Get()
        {
            return auth0Settings;
        }
    }
}

Finally, I need some configuration to send, so here is my config.json file.

{
    "Auth0": {
        "Domain": "YOUR-DOMAIN.auth0.com",
        "ClientID": "YOUR-CLIENT-ID",
        "ClientSecret": "YOUR-CLIENT-SECRET"
    }
}

Note that this is the default settings. I always want something in there so that the application doesn’t blow up. This also provides documentation on what the settings should be and the format of those settings.

Run this project and add api/settings to the end of the URI and you get the following:

blog-08092015-1

My JSON is just as I would expect it. I could, of course, ensure that the secret isn’t transmitted (but still available to the backend). However, this is good enough to test on. If you want to start here, you can download the entire project from my GitHub Repository.

Step 4: Integrate UserSecrets

Integrating User Secrets into the process is remarkably easy. It’s a single line change to the Startup.cs constructor:

        public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
        {
            var configBuilder = new ConfigurationBuilder(appEnv.ApplicationBasePath);

            configBuilder.AddJsonFile("config.json", optional: true);
            configBuilder.AddJsonFile($"config.{env.EnvironmentName}.json", optional: true);
            configBuilder.AddUserSecrets();
            configBuilder.AddEnvironmentVariables();

            Configuration = configBuilder.Build();
        }

You do need to add the Microsoft.Framework.Configuration.UserSecrets package to your project.json:

{
    "webroot": "wwwroot",
    "version": "1.0.0-beta5",
    "userSecretsId": "UserSecretsDemo",

    "dependencies": {
        "Microsoft.AspNet.Mvc": "6.0.0-beta5",
        "Microsoft.AspNet.Server.IIS": "1.0.0-beta5",
        "Microsoft.AspNet.Server.WebListener": "1.0.0-beta5",
        "Microsoft.Framework.Configuration.UserSecrets": "1.0.0-beta5",
        "Microsoft.Framework.Configuration.Json": "1.0.0-beta5"
    },

Note the userSecretsId – in the final version of Visual Studio, this is likely to be auto-generated for you. Right now, you have to do it yourself. It’s a bucket for your secrets. If you have multiple projects that all share the same secrets, you only have to set them once.

Step 5: Set up personal user secrets

Let’s set up a personal user-secret for the Domain and ClientID of our configuration file:

cd ~\GitHub\blog-code\UserSecretWeb
user-secret set Auth0:Domain shellmonger.auth0.com
user-secret set Auth0:ClientID "something with a space in it"

Note that I have to be in the project directory. More specifically, I need to be in the directory containing the project.json file. Also, you need to have the userSecretsId in the project.json file. If you don’t have that, it will complain.

You can do a user-secret list to list out the contents of the user secrets store. You can also get and destroy individual keys.

When you run your code now and go to that same /api/settings URI, you will get the following:

blog-08092015-2

However, the secrets you want to conserve will never be checked in because they are not in the git area.

Step 6: Investigate

So, where are the secrets stored? Well, with these settings, my secrets were stored in the following: %APPDATA%\Microsoft\UserSecrets\UserSecretsDemo in a file called secrets.json. There was no encryption involved, so you can just display the file.

Finally, I mentioned you might not want to show the ClientSecret – that’s for internal use and you shouldn’t be passing that around. However, the model on the server still needs it. ASP.NET uses Newtonsoft JSON.NET as a serializer, so I can tell the serializer to ignore it using a decorator in the model Settings/Auth0Settings.cs:

using Newtonsoft.Json;

namespace UserSecretWeb.Settings
{
    public class Auth0Settings
    {
        public string Domain
        {
            get;
            set;
        }

        public string ClientID
        {
            get;
            set;
        }

        [JsonIgnore]
        public string ClientSecret
        {
            get;
            set;
        }
    }
}

You can get this code, as always, from my GitHub Repository.