There are things we don’t want to make public in an ASP.NET project. The best example is the database credentials that we can often find in configuration files in plain text.
If your website is live it’s easy to end up in a situation where your web.config or some other file where you store sensitive data ends up indexed by a search engine (usually this is due to bad configuration) and then becomes extremely easy to find for someone that knows a few search tricks.
For example if you Google for filetype:config connectionString password
, there are many examples of people who inadvertently made their credentials public. A similar search on Github will yield similar results.
If you are thinking that this type of thing will never happen to you, maybe you are right, but ending up in a situation where you’ve exposed your credentials is easier than you think. For example, during development it’s very easy to check-in code to your company’s private repo with your credentials by mistake. Especially at the beginning of a new project where you are only trying things out and you might use your own username and password to connect to a local database. Because almost everyone uses the same password in more than one place, making it available to all your coworkers is not a good idea.
Thankfully ASP.NET Core introduced a new way of handling configuration information. One where your private information and your code don’t live together, and that’s a good thing.
ConfigurationBuilder
ASP.NET Core introduced a new configuration API that enables having several sources of “configuration values”. A configuration value is nothing more than a name/value pair.
Here’s an example where we are reading a list of name/value pairs from a file named appsettings.json:
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(thePathToWhereAppSettingsIs)
.AddJsonFile("appsettings.json")
.Build();
string connectionString = configuration["connectionString"];
When you are using the configuration API in an ASP.NET Core project, namely in the Startup.cs file you’ll have access to an instance of IHostingEnvironment
which has a property named ContentRoot
. You can use that as your BasePath.
This new configuration API has some other neat features, for example:
string connectionString = configuration["database:connectionString"];
Will return the value stored in connectionString under database:
{
database:{
connectionString: "The connectionString"
}
}
Even though this is an improvement over the old static ConfigurationManager class, it still doesn’t solve the problem of having secrets in files that need to be checked in to source control.
That’s where User Secrets comes in.
User Secrets
One feature of the new configuration API is that you can have several sources of name/value pairs with different priorities. For example you can create an IConfiguration instance that first reads values from environment variables and if they don’t exists tries appsettings.json:
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(thePathToWhereAppSettingsIs)
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
The last “source” being added is the one that has the highest priority, in this case it’s the environment variables.
User Secrets is one of these “sources” and it works with a tool that allows you to manage secrets through the command line.
To start using User Secrets first install the Nuget package that enables adding user secrets as a source in ConfigurationBuilder:
dotnet add package Microsoft.Extensions.Configuration.UserSecrets
Now you’ll have to edit your .csproj
file to enable the command line tool.
First add a value for UserSecretsId
in a PropertyGroup:
<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<UserSecretsId>AUniqueString</UserSecretsId>
</PropertyGroup>
Here I’ve added it in the same PropertyGroup
as the one that defines the TargetFramework
, but you can create a new PropertyGroup
and add it there if you wish.
Another change we have to do is to add the reference for the command line tool we’ll be using to generate secrets. Inside an ItemGroup
add:
<ItemGroup>
<DotNetCliToolReference Include="Microsoft.Extensions.SecretManager.Tools" Version="1.0.0-msbuild3-final"/>
</ItemGroup>
Again, you can create a new ItemGroup
or add the DotNetCliToolReference
statement to the existing one.
Unfortunately I don’t know of any other way to enable user secrets in the command line (or any other dotnet cli tool) that doesn’t involve manually editing the .csproj
file. You also have to memorize the version number. I believe this is the case even if you are using the full version of Visual Studio (I’m using Visual Studio Code).
After these changes, and making sure you have restored all the Nuget packages you can now write in the command line:
dotnet user-secrets
And you should see a help screen about how to use User Secrets.
Before we go into how to create user secrets, let’s first enable it in ConfigurationBuilder:
IConfiguration configuration = new ConfigurationBuilder()
.SetBasePath(thePathToWhereAppSettingsIs)
.AddJsonFile("appsettions.json")
.AddUserSecrets<Startup>()
.AddEnvironmentVariables()
.Build()
The important part that was added is .AddUserSecrets<Startup>()
. Startup is your startup class in ASP.NET Core, or any other class in the assembly that defines the value of UserSecretsId
.
Finally, to create a user secret, for example a “connectionString”, write in the command line:
$ dotnet user-secrets set connectionString "the connection string"
To remove it:
$ dotnet user-secrets remove connectionString
To list all secrets:
$ dotnet user-secrets list
To clear all:
$ dotnet user-secrets clear
The user secrets are saved as a json file located in %APPDATA%\microsoft\UserSecrets\<userSecretsId>\secrets.json
in Windows, ~/.microsoft/usersecrets/<userSecretsId>/secrets.json
on Linux and Mac. This file is not encrypted, and for this reason there’s a warning in the documentation saying that you should not use it in production. Same applies to environment variables.
Even though this is the case, the only recommended “production-ready” solution is Microsoft Azure Key Vault. If you don’t use Azure, or don’t want to pay for Azure Key Vault you’ll either have to encrypt the secrets yourself or find another solution (currently I don’t know of any that I can recommend). There’s also a mention right at the end of the documentation that hints that user secrets might be encrypted in the future.
Just a last quick note about using user secrets. If you change the value of a user secret while the project that uses it is running, that change will only be visible when you restart the project.