Application secrets in dotnet, a pragmatic approach
Some pragmatic guidelines for handling application secrets in ASP.NET Core projects, worked on by multiple developers using a shared code repository
Accept the config read order and use it.
ASP.NET Core Projects has access to 9 different configuration providers and they are read in the order they are specified in the startup code.
Last read configuration wins
There is a default configuration order used when Using WebApplication.CreateBuilder
- which is:
- appsettings.json
- appsettings.{environment}.json
- User Secrets
- Environment Variables
- Commandline Arguments
If building manually then preserve the default order!
Azure provided configuration settings are supplied as environment variables, and configuration setting values can be read from Azure Keyvaults if you want to be totally professional about secret handling. (Umbraco Cloud application settings uses Azure Keyvault provided configuration settings behind the scene).
Since the initial file based config files are always commited to source code at one time or another you should use
- User Secrets for adding app secrets on local machine environments
- Environment variables for adding app secrets in hosting environments
Take advantage of the settings file hierarchy and the read order
Appsettings files uses a json-hierarchy to make the config more readable and enable providing strongly typed access to groups of related settings, the runtime application config is the union of all configuration sources - which enable really intricate combinations. BUT:
Most devs will occasionally look into the appsettings file to see what settings are present in the config, and the values of said settings, support this by adding all used settings to the foundational settings file, which is appsettings.json, then specialize non-default settings in other places.
People should not insert application secret settings in appsettings.json, but if the setting is not in any settings file it is tempting for an inexperienced developer to insert the secret when they discover it is necessary for local development. And once the insertion becomes a commit and a pull request the value is in the repo history, and needs to be deleted using special procedures - instead consider handling an API config like this:
// appsettings.json
{
"MyNamespace": {
"ProductApi": {
"BaseUrl": "https://staging.myapi.net/v1",
"ApiKey": "!!!ShouldBeConfiguredAsSecret!!!"
}
}
}
// appsettings.Production.json
{
"MyNamespace": {
"ProductApi": {
"BaseUrl": "https://production.myapi.net/v1"
}
}
}
This approach makes it apparent that the ApiKey is supposed to be in the config, but that you should configure it as a secret, and as long as it’s added to the config setup as a user secret or environment value, its value will be overridden with the correct value.
To help inexperienced developers set up their machine for local development, add something like this to your README.md file along the other set up instructions:
- Copy the Staging API key for the project from Item “ProcuctApi” in the “MyCustomer” password vault. Add the staging-api key to your user secrets by running this command in your terminal in the project root directory:
dotnet user-secrets set "MyNamespace:ProductApi:ApiKey" "<insert-api-key-from-passwordvault>"
The colons in the user-secret key name replicates the configuration hierarchy
Use dotnet user-secrets for local development
This approach enables the dev to see that an api key should be added to the user secrets, when checking the appsettings file - and also leaves necessary instructions in the readme-file for everyone to get their machine ready.
It is also possible to configure user secrets using the gui in Visual Studio og JetBrains Rider and maybe even in other IDE’s - but the command line is tried and true in this scenario and works regardless of which IDE you use and what OS you’re on.
Use configsettings in Azure PaaS hosting environments
This only leaves the need for configuring the hosting environments with the proper api-key. In an ordinary Azure Web (or Umbraco Cloud setup), you can mimik the hierarchical nature of config settings by using two underscores to indicate child-nodes. So to add the api-key to the environment add a setting named MyNamespace__ProductApi__ApiKey
to the application settings.
Since Umbraco Cloud uses Azure Keyvault and configsettings behind the scenes, this naming convention also works there.
Use environment variables in server hosting environments
The same naming convention for mimicking settings-hierarchy applies when hosting in Linux and Windows server environment name your environment variable MyNamespace__ProductApi__ApiKey
and assign the API-key as value.