Search code examples
c#visual-studioasp.net-core.net-coreuser-secret

How do I use user secrets if they don't deploy with my application?


I am writing some code to take payments using Stripe. So I figured this would be the perfect time to learn about user secrets in Visual Studio as a way to securely save the API keys.

So I stored the API keys as user secrets. This way, they won't be part of the respository, and other users with access to the repository will not have access to those keys.

But now when I deploy, my app does not deploy those keys! How is this useful? Was I mistaken in thinking I could use user secrets to store my API keys? I am not using Azure.


Solution

  • User secrets are designed to be used by developers, and in particular, during development of applications running as the current user the way ASP.NET Core applications usually do.

    Older development workflows involved attaching to IIS where the application ran as an AppPool user, which required running Visual Studio as Administrator. If an ASP.NET Core application were to be developed in a similar way, user secrets would not work because they would have to be configured on the AppPool user.

    The default builders only attempt to add user secrets if the environment name is "Development" and the application has a name. The AddUserSecrets extension method then requires that the assembly has a UserSecretsIdAttribute specifying the ID of the user secrets. This usually generated by way of specifying <UserSecretsId> in the project file, but it can be added manually if you want. And finally, of course, the secrets.json file must exist in a directory by the name of the user secrets ID, which is handled simply by passing optional: true to AddJsonFile.

    The secrets.json file that stores them are located in your user profile (on Windows, it's %APPDATA%\Microsoft\UserSecrets). If you navigate to this directory, you'll probably see that the directories are GUIDs. The default is to generate a GUID for the User Secret ID, but the only real restriction is that it's a valid file system object name. Something I like to do is set it to the name of the project. Then, when I navigate to that directory in the future, it's clear which directory is for which project, and I can delete user secrets for any projects I've abandoned.

    If you're not already familiar with how the configuration system layers configuration data and merges it into a unified view, I suggest taking a look at my answer here.

    User secrets are added at a point in the stack where they override any values specified in the JSON files that are in the project structure (and in source control). Environment variables and command line arguments still override any JSON files.

    When deploying to a server, the user secrets are not available.

    Nitpick: If you have the environment set to "Development", you might be able to add secrets.json to the appropriate directory for the AppPool user, but this wouldn't work for any other environment. It's a bad idea that's not worth persuing, but I needed to point it out for completeness.

    Let's say the API you're accessing has two things to configure, the base URI and the API key.

    appsettings.json

    {
        "API": {
            "BaseUri": "https://api.example.com"
            //"Key": "<secret>"
        }
    }
    

    appsettings.Development.json

    {
        "API": {
            "BaseUri": "dev-api.example.com"
        }
    }
    

    And so on for appsettings.Test.json, etc. if you have other environments. You do not need appsettings.Production.json; that's what appsettings.json is if nothing is overridden.

    secrets.json (user secrets, per developer)

    {
        "API": {
            "Key": "this is the API key you will use during development"
        }
    }
    

    If the API is another project running in the debugger, you can override the BaseUri while you're at it. This means that appsettings.Development.json doesn't need to be edited and reverted during development. It always refers to the API on the development server, while you override it locally with user secrets. In the same way, you can point your application to any arbitrary environment's API if you're having difficulty reproducing an issue with the development API.

    secrets.json

    {
        "API": {
            "BaseUri": "https://localhost:xxxxx/api",
            "Key": "this is the API key you will use during development"
        }
    }
    

    Now, when configuring the application in IIS, you need to configure the API key as an environment variable. The base URI and other non-secret values are covered by the JSON files, so you only need to configure the truly secret values here.

    As described here, in IIS Manager, open the Configuration Editor and add environment variable "API__Key" (NB two consecutive underscores) to ApplicationHost.config (in the "From" dropdown). The value of environment variable "API__Key" can be directly retrieved using configuration.GetValue<string>("API:Key") and will map to the Key property of a class bound to configuration.GetSection("API") (ideal for use with the Options pattern).

    Once you have the environment variable in the application's configuration, it will remain there, unaffected every time you Publish from Visual Studio. You only need to go back to IIS Manager to make changes if you get a new API key.