User Secrets (Sometimes called Secret Manager) in .NET has been in there for quite some time now (I think since .NET Core 2.0). And I’ve always *hated* it. I felt like they encouraged developers to email/slack/teams individual passwords or even entire secret files to each other and call it secure. I also didn’t really see a reason why developers would have secrets locally that were not shared with the wider team. For that reason, a centralized secret storage such as Azure Keyvault was always preferable.
But over the past few months. I’ve grown to see their value… And in reality, I use User Secrets more for “this is how local development works on my machine”, rather than actual secrets. Let’s take a look at how User Secrets work and how they can be used, and then later on we can talk more about what I’ve been using them for.
Creating User Secrets via Visual Studio
By far the easiest way to use User Secrets is via Visual Studio. Right click your entry project and select “Manage User Secrets”.
Visual Studio will then work out the rest, installing any packages you require and setting up the secrets file! Easy!
You should be presented with an empty secrets file which we will talk about later.
Even if you use Visual Studio. I highly recommend at least reading the section below on how to do things from the command line. It will explain how things work behind the scenes and will likely explain away any questions you have about what Visual Studio is doing under the hood.
Creating User Secrets via Command Line
We can also create User Secrets via the command line! To do so, we need to run the following command in our project folder :
dotnet user-secrets init
The reality is, all this does is generate a guid and place it into your csproj file. It looks a bit like so :
<UserSecretsId>6272892f-ffcd-4039-b82a-b60874e91fce</UserSecretsId>
If you really wanted, you could generate this guid yourself and place it here, there is nothing special about it *except* that between projects on your machine, the guid must be unique. Of course, if you wanted projects to share secrets then you could of course use the same guid across projects.
From here, you can now set secrets from the command line. It seems janky, but unfortunately you *must* create a secret via the command line before you can edit the secrets file in a notepad. It seems annoying but.. That’s how it works. So in your project folder run the following command :
dotnet user-secrets set "MySecret" "12345"
So.. What does this actually do? It’s quite simple actually. On Windows, you will have the following file :
%APPDATA%\Microsoft\UserSecrets\{guid}\secrets.json
And on Linux :
~/.microsoft/usersecrets/{guid}/secrets.json
Opening this file, you’ll see something like :
{ "MySecret" : "12345" }
And from this point on you can actually edit this file in notepad and forget the command line all together. In reality, you could also even create this file manually and never use the command line to add the initial secret as well. But I just wanted to make note that the file *does not* exist until you add your first secret. And, as we will see later, if you have a user secret guid in your csproj file, but you don’t have the corresponding file, you’ll actually throw errors which is a bit frustrating.
With all of this, when you use Visual Studio, it essentially does all of this for you. But I still think it’s worth understanding where these secrets get stored, and how it’s just a local file on your machine. No magic!
Using User Secrets In .NET Configuration
User Secrets follow the same paradigm as all other configuration in .NET. So if you are using an appsettings.json, Azure Keyvault, Environment Variables etc. It’s all the same, even with User Secrets.
If you installed via the Command Line, or you just want to make sure you have the right packages, you will need to install the following nuget package :
Install-Package Microsoft.Extensions.Configuration.UserSecrets
The next step is going to depend if you are using .NET 6 minimal API’s or .NET 5 style Startup classes. Either way, you probably by now understand where you are adding your configuration to your project.
For example, in my .NET 6 minimal API I have something that looks like so :
builder.Configuration.AddEnvironmentVariables() .AddKeyVault() .AddUserSecrets(Assembly.GetExecutingAssembly(), true);
Notice I’m passing “true” as the second variable for UserSecrets. That’s because in .NET 6, User Secrets were made “required” by default and by passing true, we make them optional. This is important as if users have not set up the user secret file on their machine yet, this whole thing will blow up if not made optional. The exception will be something like :
System.IO.FileNotFoundException: The configuration file 'secrets.json' was not found and is not optional
Now, our User Secrets are being loaded into our configuration object. Ideally, we should place User Secrets *last* in our configuration pipeline because it means they will be the last overwrite to happen. And… That’s it! Pretty simple. But what sort of things will we put in User Secrets?
What Are User Secrets Good For?
I think contrary to the name, User Secrets are not good for Secrets at all, but instead user specific configuration. Let me give you an example. On a console application I was working with, all but one developer were using Windows machines. This worked great because we had local file path configuration, and this obviously worked smoothly on Windows. However, the Linux user was having issues. Originally, the developer would download the project, edit the appsettings, and run the project fine. When it came time to check in work, they would have to quickly revert or ignore the changes in appsettings so that they didn’t get pushed up. Of course, this didn’t always happen and while it was typically caught in code review, it did cause another round of branch switching, and changes to be pushed.
Now we take that same example and put User Secrets over the top. Now the Linux developer simply edits their User Secrets to change the file paths to suit their machine. They never touch appsettings.json at all, and everything works just perfectly.
Take another team I work with. They had in the past worked with a shared remote database in Azure for local development. This was causing all sorts of headaches when developers were writing or testing SQL migrations. Often their migrations would break other developers. Again, to not break the existing developers flow, I created User Secrets and showed the team how they could override the default SQL connection string to instead use their local development machine so we could slowly ween ourselves away from using a shared database.
Another example on a similar vein. The amount of times I’ve had developers install SQL server on their machine either as /SQLExpress or /MSSQLSERVER rather than a non-named instance. It happens all the time. Again, while I’m trying to help these developers out, sometimes it’s easier to just say, please add a user secret for your specific set up if you need it and we can resolve the issue later. It almost becomes an unblocking mechanism that developers can actually control their own configuration.
What I don’t think User Secrets are good for are actual secrets. So for example, while creating an emailing integration, a developer put a Sendgrid API key in their User Secrets. But what happens when he pushes that code up? Is he just going to email that secret to developers that need it? It doesn’t really make sense. So anything that needs to be shared, should not be in User Secrets at all.
Just how do you deal with secrets that should be shared? The whole point of user secretes being a file in the home folder and not the project folder so that the file isn’t pushed to e.g. Git with other files, making it vulnerable to “public” exposure. This is why we don’t store shared secrets in appsettings.json, because that is normally pushed to Git with the rest of the source because it contains valuable, non-secret config data that should be shared.
For shared secrets, you should use a centralized secrets manager such as Azure Keyvault / AWS Secrets Manager etc.
I’ve been playing with adding in an additional local settings json and excluding all such files in the gitignore. I have and am using user secrets but I’ve never really liked them. Existing outside the repo I forget they are there and tracking them down with the id is sort of annoying if you don’t have VS to do it for you and you forget where they are, etc.
One could do something fancier but I’ve simply added an appsettings.Development.Local.json file to my start up after the stock appsettings files allowing me to have a default appsettings.Development.json for standard dev settings I want to share and allowing the appsettings.Development.json to essentially document what must to go into appsettings.Development.Local.json for your local only secrets, settings, config overrides, etc. I borrowed the idea from a Ruby of Rails repo I saw which did a similar thing with .env files by including a .env.example you would rename and fill in.
It has the downside of being in the repo making it is possible to commit accidentally if someone breaks the gitignore or misbehaves. But it has the merit of being right there in my IDE or project directory structure if and when I need it.