When building .NET applications you are guaranteed to encounter a need to store sensitive information like a connection string or an access token. Within the .NET world, you have a number of different options to ensure good secret management.
Hard-coding and committing sensitive data within source control is a definite no-no. This means that on most projects, you will need to pick the secret management technique that is most appropriate for that use case.
Within this article, you will learn about the different options at your fingertips as well as get a rough implementation guide. If you want to master secret management within your application, read on 🔥🔥🔥
When building a .NET app, the majority of your settings will be stored within
appsettings.json, so this could be an option to also store sensitive data. The obvious issue with using
appsettings.json is the lack of security. If you hardcode access tokens and secrets as plain text in
appsettings.json, if your application got compromised either on the server or, within source control, anyone would be able to view that sensitive data.
For some projects, this lack of security might be an acceptable trade-off. When I work on my own hobby projects I would typically hard-code a connection string in config on a server, however, I would be a lot more cautious when working on an enterprise-level project. The obvious benefit of hardcoding secrets within
appsettings.json is simplicity. Launch your app and you get access to your settings in code using native .NET code.
Typically, when you go down this path, you will need to create environment-specific
appsettings.json files, so you might have an
appsettings.staging.json or an
appsettings.production.json file, you could even create sensitive-data specific settings files. To do this within Visual Studio, you will create different environments and then you would add a little bit of code within your
program cs that will read in the correct environment config file on application start. That code would be added to
CreateDefaultBuilder() and could look like this:
The code snippet above will try to load an environment-specific config file based on an environment variable. After that, it will try to read a specific config file called
appsettings.private.json. Within the real world, you will likely use one or the other technique, however, being able to compare the two approaches hopefully gives you insights.
If you go this route, you will also likely want to exclude your environment/sensitive config files from source control. Doing this means that if your source control was compromised, or, you simply want to hide secrets from certain team members you can. The easy way to hide files is not to commit them in the first place by adding rules within
.gitignore is OK, however, it is not fool-proof. Imagine, you add sensitive data within code during development for testing purposes. Eventually, you refactor the code and remove the sensitive data to make it production-ready/you might even add a rule in
.gitignore. The issue is that while you were working, you will likely have committed and pushed your code several times. Even if you remove the secure data within your latest branch, your secret data could still be exposed within your commit history for people to find. Yes, this has happened to me before and it's an easy one to miss!
A final consideration is that often, you may have data in a configuration file that has a mix of normal data and sensitive data. When you omit the whole file, you are also making your architecture a little harder to deal with non-sensitive data.
Environment variables probably go hand in hand with config management, remember I used one in the code snippet above 😊 When you make of use an environment variable, whenever you need access to its related value you will write code using the appropriate special syntax to access it. When it comes to using environment variables, you will need to make sure that you set them all on every single environment that your application will be run on. This will include your local device, as well as staging and production environments. The next consideration is how you deploy these excluded config settings during deployment. You still need access to the settings after all!
The .NET Secret Manager
Another option is to use an out-of-the-box .NET feature to make secret management more secure. The Secret Manager tool ships with the .NET framework and its purpose in life is to improve the management of sensitive data during development. The secret manager tool is definitely a better safeguard compared to config files, however, the secret manager does have limits when it comes to production use.
I will walk you through how you can use this tool shortly, however, the important thing to note is that when you use Secret Manager, your sensitive data will be stored locally outside of your solution folder. Assuming you are using Windows, any secrets that you add within the manager will be stored in a JSON file within a folder located under your Windows user profile temp storage folder. This folder can be located on your local system here
After adding a secret, if you look at the JSON you will notice that the values are not encrypted but instead stored in plain text. This means that it is up to you to judge how happy you are with these risks. As the secret management file is located outside your applications solution folder it means that your sensitive data is guaranteed to be excluded from your GIT repo. This is definitely safer than storing data in config, however, if you have concerns that someone could access your local development environment your sensitive data could be exposed.
Getting started with this tool is simple, you can create and manage secrets either via the CLI or within Visual Studio by right-clicking on the project in the Solution Explorer and selecting
Manage User Secrets. To get started with Secret Manager, the first thing you need to do is enable it on your project. You can do this using this CLI command:
You can verify this command worked, as your project's
csproj file should be updated with a property called
UserSecretsId. With that done you can add properties and sensitive data by manually updating the JSON file, or via the CLI using this command:
After you have enabled the secret manager when your application starts and after you call
Program.cs, the framework will automatically read the properties from this file and add them to the .NET
Configuration object. You can learn more about this tool from the official Microsoft blog here, however, the key thing is that you can access value like this:
Another thing to note is a hierarchy, within your
secrets.json you can create objects with child-objects and so on. To access a value that is buried within a level of hierarchy you use the
: character within the code that references the key. Take this JSON:
To access the value of
GRANDCHILD in code, just like accessing a normal app setting, you could use this key
START:CHILD:GRANDCHILD. Another management command worth mentioning is the list command:
The secret manager is definitely a more secure process, however, it does introduce slightly more set-up faff. When someone pulls a repo they will need to know they have to install and configure the secret manager. This means good documentation is important. The issue with this technique is that unless you are deploying to a physical server you control, you will not have a way to manage secrets in production.
Cloud-Based Secret Management
The secret management solutions that we have reviewed so far have all involved a level of configuration locally. The next potential set of secret managers differs as they are cloud-based. Regardless of where you host your app, all popular cloud providers provide their own secret management tool.
- AWS has Secret Manager
- Google Cloud has Secret Manager
- Netlify Secret Controller
- Azure has Key Vault
The good thing about using a cloud-based tool is that you can easily access and share secrets locally as well as via any server your application might run on. Using a secret manager means all your secret values will be encrypted. All cloud managers provide authentication and authorization capabilities, meaning you can have fine-grained control over who can add and edit secrets via the online portal. Out of the options, a cloud-based secret manager is probably the most robust option to handle sensitive values within your software, however, it does come with some trade-offs, which are mainly hardware-based.
One thing to consider is cost. Hard-coding sensitive data within your codebase is free, while cloud-based secret managers all come with an ongoing cost. Cloud-based secret managers will not be the most expensive hardware within your set-up but for smaller, hobby projects it might add an unneeded cost.
Another trade-off is availability. If your password manager becomes unavailable, your application will fall over. This has two downsides. First, when working locally, you will always need an active connection to your cloud vault, otherwise, your application won't work. This is OK for 99% of the time, but, what happens if you need to work on a train with a patchy internet connection, or, what happens if your internet dies... you will be blocked from working!
Second, is disaster recovery. When you use a cloud-based secret manager it has to be available. If it is not your whole application might break. This means you need to add your secret manager to your disaster recovery plans and your password database needs to be backed up regularly. Also, as data is being transmitted over the net, you might need IP restrictions, firewall rules, etc...
In essence, there is a trade-off between security and complexity. Cloud-based managers are the most robust in terms of securing your software, however, it does complicate the hardware side of things. From my experience, adding a cloud service is the best option, however, it will incur costs. Most secret managers aren't that expensive, however, if you are working on a free hobby project where budgets are tight. The added protection might not be worth the added costs. For some projects, this cost might be insignificant compared to the risk of your application being hacked!
Another odd quirk I found with dealing with sensitive data on projects is that sometimes being too locked down causes its own problems. Let's say that you work in a team that gets broken up or a key team member quits. It is all too common that after people leave, secrets get forgotten. Over the years I've wasted lots of time re-generating keys or trying to track down the underlining value.
Custom Database / CMS System
The final solution to managing secrets within an application is an often overlooked one. If your website or mobile app is powered via a custom SQL database, a non-SQL database, or even a CMS system, you could store your sensitive data there. Over the years I've used tools like MongoDB and SQL to manage settings, however, I personally prefer using a CMS if I can. The reason for this is that most CMS systems will ship with properties that will allow you to model a password or a secret within the CMS. Your CMS database will be backed up, so DR is easy. Also, most CMS systems will have version management, so you can roll back values if needed. Finally, access to the secrets can be managed, as all CMS systems have members and roles, so you get authorization and authentication. Finally, a lot of CMS systems will allow you to work on your own local database, meaning, you can work locally and offline without impacting anyone else!
Happy Coding 🤘