Keeping Secrets in ASP.NET’s appsettings.json
A colleague, Jonathan, and I maintain an ASP.NET Core application. Like many ASP.NET projects, our application needs SQL connection strings and other credentials to connect to back end services. These credentials allow access to sensitive data, that most people on our team have no need to access. Therefore, these credentials should be kept secret, being visible only to the application while running in production.

We stored our app’s secrets in a Google Cloud Storage object, which encrypts objects at rest. Google Cloud Storage gave us the access control we needed, but it was not a perfect fit for our needs because we were vulnerable to the following mistakes:
- Race conditions. A secret could be lost in the following scenario:
- 1. I download the secrets file and add a secret.
- 2. Jonathan also downloads the secrets file and adds a secret.
- 3. I upload my modified version of the secrets file.
- 4. Jonathan uploads his modified version of the secrets file.
- 5. The secret I added is lost. Because we turned on Cloud Storage’s object versioning, we would still be able to recover from this scenario once we discovered what happened, but it would take some work to notice the issue and then fix it.
- Version Mismatch. The secrets file stored in Google Cloud Storage has its own version history. Does version 1 in the Google Cloud Storage correspond to version 1 of the application? How do you change a secret for the next version of the application, but not affect the current running application? Tracking versions and avoiding pitfalls required planning ahead and careful engineering.
There is already a solution for the issues listed above: version control. Git, subversion, mercurial and many other version control systems are designed to solve exactly these problems. We did not want to reinvent the wheel. We were tempted to keep secrets in version control, but learned that keeping secrets in version control is terrible practice. If we stored the secrets in version control, then anyone on our team could easily see the secrets. If we were keeping our code in a public repository on GitHub, then the whole world would be able to see our secrets.
Key Vaults

One reasonable solution, described by Scott Hanselman and others, is to use a key vault, that has its own versioning built in. A key vault with versioning prevents some race conditions and makes it easy to recover from accidental corruption. A key vault provides strict access controls so people who don’t need access to the secrets to do their jobs can’t see it. Our first solution above effectively used Google Cloud Storage as a key vault.
However, key vaults still suffer one shortcoming: multiple version histories. Which version of the app needs which version of the secret? What about branches? Version mismatches are bound to happen, resulting in broken applications and broken user experiences.
Encryption

Another good solution has already been discussed by Justin Ellingwood, John Resig, and many others. Basically, the solution is to encrypt secrets in a file and store only the encrypted file in version control. This solution combines the full power of the version control solution with access control provided by encryption. Secrets are still safe because only people with access to the private key can decrypt them.
This solution avoids the version mismatch issue that occurs with key vaults, because there is only a single version history: the one kept in version control.
However, this solution still suffers a couple shortcoming:
- Secrets are not as locked down as they are with key vaults. Whoever is building the application for production must be able to decrypt the secrets. The application builder never calls the back end services, therefore should not have access to the secrets. Additionally, the build product, be it a binary file or docker image, contains the secrets in plain text, so the build product must be protected in the same way as the secrets themselves.
- Key files must be carefully managed, rotated, and distributed. Who needs a key file? Where are all the key files? How do you issue a new key when a key file is believed to have leaked?
A Better Solution

Using Google Cloud Key Management Service (KMS), we built a solution that overcomes the shortcomings of the two solutions described above. Secrets are encrypted and stored in version control, so there is a single version history. Version mismatches cannot occur with only a single version history. But unlike the solution above, build products never contain plain text secrets. Therefore, secrets remain hidden until the application is fully deployed on its production host. Even the agent building and deploying the application does not have the necessary keys to decrypt the secrets.
Additionally, keys are never delivered to developers’ machines, even when encrypting and decrypting the secrets. Encryption and decryption happens on Google’s servers, and users must use their own personal credentials to encrypt or decrypt.
KMS has built-in, automatic key rotation as well, so if a key is accidentally leaked, only a limited snapshot of secrets will be compromised.
ASP.NET Core’s configuration is well designed, and made it easy for us to implement a custom IFileProvider that decrypts files in memory. With the custom IFileProvider, accessing secrets from the application is as easy as accessing an ordinary configuration variable in appsettings.json.
Secrets are authored in a file called appsecrets.json, which lies in the same directory as appsettings.json. Of course, appsecrets.jsonmust never be checked into version control, so there’s a.gitignore file to prevent that from happening:
A powershell script uses the Google Cloud SDK to encryptappsecrets.json to appsecrets.json.encrypted:
3 lines of code in the .csproj ensure the encrypted files are delivered with the binary:
And one statement in Program.cs loads the encrypted secrets:
And now, secrets can be used in the application like any other configuration variable:
When deploying to Google Kubernetes Engine, Google Compute Engine, and Google AppEngine Flexible Environment, the host machines already have the Google credentials necessary to decrypt the secrets. When deploying to another environment, install a Service Account Key and set the environment variable GOOGLE_APPLICATION_CREDENTIALS on the host machine, and then that host will also have access to the secrets.
Wrapping Up
With version control and Google Cloud Key Management Service (KMS), we have built a system that’s easy to use, easy to maintain, and as secure as a key vault solution.
Take a look at all the source and project files at https://github.com/GoogleCloudPlatform/dotnet-docs-samples/. Security through obscurity has been proven to fail, so if you see any security issues with our design or implementation, please leave a note here or file a bug on the github repository.
Although the Google Cloud Key Management Service (KMS) API is fully released and supported, the client library is still in beta. So, you may want to wait until the 1.0 client library is released before employing this solution in production.

