Keep your ASP.NET Core application’s secrets safe during development
You’re building an ASP.NET Core web application and you realise you need to access sensitive configuration details such as passwords, connection strings etc.
You need configuration values for your Development environment (e.g. connection details for your local database etc.) and different values for use in Production.
Why not just store your secret values in the code?
You could just put hard-coded magic strings in your code, but this is bad for all sorts of reasons (for a start, it’s not very secret!)
You’re almost certainly going to need different values for different environments.
Storing all of these in code (or even configuration files) and “choosing the right one” is asking for trouble.
Committing these secrets to your source repository means anyone with access to the code has access to all of the environments.
It’s all to easy for you (or another developer) to accidentally (or deliberately, let’s say to debug a live issue) connect to the staging/production instances of the database, at which point mistakes are very easily made…
Of course there’s also the possibility that you’re working on open-source software. The chances are, whilst the code may be open, you don’t want all your secret API keys and database connection strings visible to anyone who browses the code on Github.
Storing your secrets using .NET Core
So what’s the alternative? how can you keep your secrets safe using .NET Core?
.NET Core handles this using something called the Secret Manager Tool. With it, you can store your secrets outside of your application’s source code as a series of JSON key-value pairs.
Crucially, the values are stored somewhere else on your machine, not in your source code.
These secrets are then easily accessed in code (by specifying the name of the secret).
When it comes to running your app in production, you need another place to store these secret values.
If you’re running on Azure, your app can easily retrieve the values from the Azure Key Vault. This has the added benefit that it’s easy to change these values without re-deploying your application.
Setting it up
You’re going to need to…
Identify your secrets e.g. “SuperSecretConnectionString”
Store values for use when building/testing the app on your own machine
Securely store different values where your app can access them in production e.g. Azure or Self-Hosted
Access your secret values from your app (irrespective of where they’re stored)
Identify your secrets and provide values for use during development
If you’re using Visual Studio 2017, you can easily manage your secrets by right-clicking on your project and selecting the aptly-named “Manage User Secrets” menu item.
This then opens up your secrets.json file. Don’t go looking for this file in your project folder as you won’t find it.
The actual location might vary but it’s typically found in %APPDATA%\microsoft\UserSecrets\
.
With that file open, it’s just a case of typing in your key-values. Here’s an example.
{
"googleClientId": "YOUR_GOOGLE_CLIENT_ID_GOES_HERE",
"googleClientSecret": "YOUR_GOOGLE_CLIENT_SECRET_GOES_HERE",
"openWeatherApiKey": "YOUR_OPENWEATHER_API_KEY"
}
In this example we’ve a couple of keys for using OpenId connect authentication via Google and also an API key for accessing the Open Weather API.
Bring in your secrets
Sooner or later you’re going to need to retrieve the secret values.
To do so, you’ll need to make some changes to Startup.cs
.
Start by installing the Microsoft.Extensions.Configuration.UserSecrets package.
Install-Package Microsoft.Extensions.Configuration.UserSecrets
Now modify startup.cs as follows…
public Startup(IHostingEnvironment env)
{
// ------------
if (env.IsDevelopment())
{
builder.AddUserSecrets<Startup>();
}
Configuration = builder.Build();
// -------------
}
public IConfigurationRoot Configuration { get; }
This tells .NET Core to locate the application that houses the Startup
class and add any configuration values it finds in that application’s secret store (the secrets.json file we saw earlier).
You might be wondering how it locates the correct secrets for your application.
When you chose to “Manage User Secrets”, Visual Studio ensured that your project’s .csproj file had a UserSecretsId property (if none was found, it added one).
<PropertyGroup>
<PackageTargetFallback>$(PackageTargetFallback);portable-net45+win8+wp8+wpa81;</PackageTargetFallback>
<UserSecretsId>ea585993-1b3d-4ba5-9037-92bb4ddf3930</UserSecretsId>
</PropertyGroup>
The changes to Startup
instruct .NET Core to add any user secrets it can find for this application.
Typically it will dive off to find %APPDATA%\microsoft\UserSecrets\!USER_SECRETS_ID!\secrets.json
where !USER_SECRETS_ID! refers to the value specified in your csproj file.
e.g. %APPDATA%\microsoft\UserSecrets\ea585993-1b3d-4ba5-9037-92bb4ddf3930\secrets.json
Access your secrets
All that’s left, is to actually use these secret values in your app.
If you just want quick access to a value, you can get the value via Configuration
in your Startup
class.
Here’s an example from the Weather Checker app’s Startup.cs.
ClientId = Configuration["googleClientId"],
ClientSecret = Configuration["googleClientSecret"],
This relies on the key (e.g. googleClientId) being found in Configuration.
Remember, Configuration brought in values from User Secrets for this app. If the key is found, the relevant value will be returned.
But what if you want to access a value elsewhere in your app, maybe from a controller.
In that case, you might want to look at implementing IOptions
.
Strongly Typed access to your secrets
You can create a class that represents your configuration, then populate an instance of this class from your User Secrets.
This saves you from using magic strings everywhere you want to access a secret.
You’ll need a class that represents your configuration values.
Here’s an example.
namespace WeatherStation.Controllers
{
public class AppOptions
{
public string OpenWeatherApiKey { get; set; }
}
}
We’re going to set this up so that any matching secret value (where the key matches the property name e.g. OpenWeatherApiKey) will be made available via this class.
Modify startup.cs as follows…
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// -------
services.Configure<AppOptions>(options => Configuration.Bind(options));
// Add framework services.
services.AddMvc();
// -------
}
This will take any Configuration values (including those found in our User Secrets) and bind them to matching properties in your AppOptions
class.
Now head on over to a controller (or any other class) and inject IOptions into the constructor.
private readonly IOptions<AppOptions> _options;
public WeatherController(IOptions<AppOptions> options)
{
_options = options;
}
Now you can easily access any configuration values that you expose via the AppOptions
class.
[HttpGet("[action]/{city}")]
public async Task<IActionResult> City(string city)
{
var weatherApiKey = _options.Value.OpenWeatherApiKey;
// ----
}
Your turn
Now you know why it’s generally bad to store your sensitive configuration values in code, and how to store them in a more secure place.
If you’ve got an ASP.NET Core app with connection strings or other config values in code (or config files), try swapping them out for user secrets and let me know how you get on.
Next up, we’ll take a look at storing these secrets for use in Production.
photo credit: bmward_2000 The Vault via photopin (license)