The Secrets of ASP.NET Core User Secrets

Comments 0

Share to social media

The imminent advent of the GDPR serves to remind us once again of the importance of keeping any sensitive data that is managed by applications safe and secured. It is no longer a task that you can duck because it isn’t that interesting.

In classic ASP.NET applications, we had a relatively easy API that we could use to encrypt the content of selected sections of the web.config file. In this way, you could encrypt information such as connection strings. I have the impression that this API, along with the more general need to encrypt configuration data, has lost its appeal and attraction over the past few years. In fact, the success of source code platforms such as Github has increased this trend to lose interest in encryption. Consequentially, a great deal of potentially critical data such as API keys and connection strings have been published as clear text in public repositories.

In ASP.NET Core, the entire infrastructure for managing and securing configuration data has been rewritten from scratch. The web.config file has disappeared to be replaced by a variety of data providers. None of them, though, has encryption capabilities. However, ASP.NET Core has user secrets.

Application’s Sensitive Data Defined

Simply put, a user secret is any application data that you want to keep secret. Canonical examples of software user secrets are database connection strings, API keys, client IDs and secrets to access social networks and SaaS applications. User secrets also include all those pieces of information that are sensitive and application-specific, such as credentials to access payment gateways, but also tokens to access online services.

In these enthusiastic days of shared code repositories, the safest thing to do is to keep secrets outside the scope of the code repository, whether it be TFS, Github or whatever else. In ASP.NET Core, user secrets are, by design, stored outside the project so that the project can easily and quickly be checked-in and published also on public repos without worries and anxiety. Let’s see how to deal with user secrets in an ASP.NET Core applications and within Visual Studio 2017.

Dealing with Secrets in Visual Studio

In an ASP.NET Core project, you will need to reference the secret manager tool in order to work with user secrets from within Visual Studio. The secret manager tool comes by installing the Nuget package Microsoft.Extensions.SecretManager.Tools. The tool can be used from the command line -through the dotnet launcher – or silently used by Visual Studio whenever you, the developer, sends commands through the UI. The secret manager controls a local machine data store where the secrets are saved. To enable the manager to create the store, you need to generate a unique secret manager ID for the application. The ID is an arbitrary string with the sole requirement of being unique in the development machine. You can set the secrets ID by directly editing the CSPROJ file of the project. You add a UserSecretsId entry in the PropertyGroup section.

You can also use the assembly directive but, if you do so, there’s the risk that the ID gets duplicated and, in this case, the application won’t even start.

The fact is that Visual Studio automatically edits the CSPROJ by file adding a random GUID as the secrets ID the first time you use the UI to add a secret. The figure below shows the relevant menu item.

You open up an editor window by clicking on the ‘Manage User Secrets’ menu item. Basically, your secrets are saved as plain properties in a JSON file named secrets.json. The file is created automatically and edited from a system protected folder on the local machine. The exact location depends on the operating system. For a Windows machine, the file is:

The %APPDATA% folder is a system folder, hidden by default, under the user’s profile. On other operating systems, specifically Linux and Mac, the location is under the Microsoft folder. Note that changing the user secret ID (in the CSPROJ file or in the assembly directive) will generate a new, empty secrets.json file.

Considerations about the Secrets.json File

As weird as it may sound, all of the application secrets are being kept in a plain, crystal-clear JSON file. Is this really an issue? It depends on the perspective you take. If your viewpoint is that secrets are secrets regardless of the environment then, yes, having them saved in a JSON file is not secure at all. However, the secrets file lives on the development machine in your user area, and only there. It is not part of the project and there’s no risk it will be inadvertently checked-in in some repositories. At the same time, Visual Studio offers a simple user interface that transparently retrieves and manages the secrets for you. This is designed to make you more likely to resist the temptation of having it available at hand but dangerously, right there in the project,

What matters is that the secret manager and user secrets are, by design, for development only. Sure, the secrets could have been saved in some machine-specific trusted store or at least encrypted with some transparent and machine specific key. For the time being, though, that’s not the case especially because user secrets—as configured—won’t work in production. There is no realistic risk of losing data unless your laptop is stolen and your login password is then guessed. However, it is not a full solution for the production environment either.

So let’s see how to programmatically deal with user secrets, in development first and in production next.

Using Secrets in the Development Environment

User secrets are, technically, part of the configuration tree and are loaded by processing a JSON file—the secrets.json file—from a protected and hidden location. Here’s the constructor of a Startup class that supports user secrets.

The MyAppSecretConfig class is a plain POCO class whose public interface fully matches the list of secrets. At the very minimum, the class is like the one below.

The content of the secrets.json file is simply mapped to the matching members of the class. It’s the same matching algorithm that is used with model binding. You inject an instance of the class wherever you need to access secrets using the internal DI system and the IOptions<T> interface.

As mentioned, though, the user secrets are a development-only feature. Also, just because secrets are stored only on the developer’s machine, the content must be the same for any developers in the team. At the same time, and for the same reason, the list of secrets being used on a machine can’t simply be checked into a shared repository.

Configuration for the Production Environment

User secrets don’t exist in production, yet it is in production especially that you need the information that secrets hold. There are a couple of possible approaches, plus the definitive solution of writing your own encrypted configuration data provider. (I’ll address just this in an upcoming article.) The simplest way to store application sensitive settings in production is by using the application settings of an Azure App Service.

The interesting thing here is that Azure makes all the values of the app settings available as environment variables. Therefore, if you use user secrets in development then you should have AddEnvironmentVariables added in production to the building of the configuration tree.

Azure application settings are not encrypted, but at the same time you store values there so you could even encrypt them in some way before storing them. When encryption is involved, the main issues are most important but .NET Core provides interesting helpers also in that area. For more information you might want to check the following URL: https://docs.microsoft.com/en-us/aspnet/core/security/data-protection.

An alternate approach is to use an Azure Key Vault. In this case, you use the AddAzureKeyVault extension method to the ConfigurationBuilder class. The key vault offers a better guarantee of security because the method of access to its content complies with the FIPS 140-2 cryptographic standard. The AddAzureKeyVault method takes the URL to the vault as argument and either a X509 certificate or credentials to an Azure AD account to identity the client of the service. The Azure key vault also exposes a REST API for you, so you can extend its use beyond the storing secrets and even consider storing information in it programmatically. Secrets are essentially static content that is set once for the life of the application instance, whereas the key vault is just a particularly safe data storage. For more information on Azure key value, refer to the following article: https://docs.microsoft.com/en-us/aspnet/core/security/key-vault-configuration?tabs=aspnetcore2x.

If your application is not hosted on Azure, but instead lives on premises, the environment variables are still a good option to use to store user secrets, and having a dedicated encrypted configuration provider also works in this case.

User Secrets Before ASP.NET Core

Even if this article is primarily focused on ASP.NET Core it might helpful to recall how to protect user secrets (e.g., connection strings) in classic ASP.NET MVC. It is interesting to note that this feature has long been available. I’s been there since ASP.NET 2.0, released back in 2005, but for some reasons it was hardly used in recent years. Even now when I happen to mention it in conference sessions or classes I still find quite a few developers completely unaware of it.

In classic ASP.NET, configuration data is typically stored in a section of the web.config file and the content of configuration sections can be encrypted using one of the Windows encryption library. There are two main options: using the DPAPI library or the RSA library. The former saves you from the burden of dealing with keys as it uses a machine specific autogenerated key. The latter is more portable across machines

Although there’s an API for encrypting and decrypting sections of the web.config file, in your code you’re mostly interested in decrypting configuration data. As far as encrypting is concerned, you might want to rely on external tools that do the job for you. It can be of course a custom tool you write personally—something as simple as a console application—or it can be a system tool such as aspnet_regiis.exe. You will be able to find this command line tool in the .NET Framework system folder.

The exact syntax to use is the following

The -pe argument indicates that you want to encrypt the connectionStrings section of the configuration file within the /YourApp application. The default encryption provider is the RSA. For this command line to work, the application must be reachable with the given relative URL. Otherwise, if the application is not published yet, you can use a programmatic API to do the same.

Add this code to a controller method and you’ll get the web.config turn into something like below.

You read it back, you just use the regular API for reading a connection string and decryption will be transparent.

To decrypt the file and have it back to the clear format you simply change the method ProtectSection with UnprotectSection.

Summary

It is very important to encrypt sensitive data but, overall, I feel that over the past few years developers perceived it as a low priority task. In ASP.NET Core, Microsoft recently introduced ‘user secrets’, which is an API that aims to keep sensitive data out of the project folder. This means that if any code in the project is checked into some code repository, there will be no sensitive data included with it because those secrets are kept outside the project and stored in a user’s profile subfolder. User secrets are only intended for development work and are not a trusted store because their content is unencrypted. Azure application settings or Azure key vault are the options to save secrets within Azure. Finally, we looked back at non-Core ASP.NET and found out that the encryption of sections of the web.config file is a feature that has been available since ASP.NET 2.0. There are no more excuses that would permit you to skip the chore of encrypting or protecting in some serious way any sensitive data such as connection strings and API keys.