Anthony Chu Contact Me

Keeping Secrets Safe in ASP.NET Core with Azure Key Vault and Managed Service Identity

Monday, October 9, 2017

In Azure, the recommended place to store application secrets is Azure Key Vault. ASP.NET Core makes it easy for an application to read secrets from Key Vault, but the application needs to be given valid credentials to do so. These credentials are often stored in plain text in an app setting, allowing anyone with access to the application to see them.

Managed Service Identity (MSI) was created to solve this problem. We can use it to access Key Vault without storing any secrets or credentials information in our web application.

Today, we'll take a look at how to use ASP.NET Core with MSI and Key Vault to properly secure and access secrets.

Enable Managed Service Identity (MSI)

Before Managed Service Identity, if we wanted to access secrets in Azure Key Vault from a Web App, we needed to create a service principal and provide the service principal's client secret to our application.

MSI automatically creates and manages a service principal for an Azure App Service resource, such as a Web App. This makes it simple to read secrets from Key Vault, especially from an ASP.NET Core application.

To enable MSI, we need to browse to our Web App in the Azure portal, open the Managed Service Identity tab, and enable it.

Enable MSI

A service principal for our application is automatically created for us in Azure Active Directory.

Configure Azure Key Vault

We now need to add a policy to our Key Vault to grant access to our application. Only List and Get permissions on secrets are required.

app policy

Next, we will add a couple of secrets for our app to read.

Add secrets

Tip: Refer to this example to learn how to deploy the Web App and Key Vault from a single Resource Manager template.

To deploy everything in this article in one click, use the Deploy to Azure button at the bottom of this article.

Configure ASP.NET Core

The easiest way to work with MSI from an ASP.NET Core application is to use the Microsoft.Azure.Services.AppAuthentication package.

We need to add a few lines of code to our Program.cs file:

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((ctx, builder) =>
        {
            var keyVaultEndpoint = GetKeyVaultEndpoint();
            if (!string.IsNullOrEmpty(keyVaultEndpoint))
            {
                var azureServiceTokenProvider = new AzureServiceTokenProvider();
                var keyVaultClient = new KeyVaultClient(
                    new KeyVaultClient.AuthenticationCallback(
                        azureServiceTokenProvider.KeyVaultTokenCallback));
                builder.AddAzureKeyVault(
                    keyVaultEndpoint, keyVaultClient, new DefaultKeyVaultSecretManager());
            }
        })
        .UseStartup<Startup>()
        .Build();

private static string GetKeyVaultEndpoint() => Environment.GetEnvironmentVariable("KEYVAULT_ENDPOINT");

Although MSI manages the service principal for us, we still need to know our Key Vault's URL. Here, we're looking in an environment variable called KEYVAULT_ENDPOINT for this value.

If the Key Vault endpoint is configured, we use AzureServiceTokenProvider from the Microsoft.Azure.Services.AppAuthentication package we brought in earlier to create a Key Vault client. We pass that client to the AddAzureKeyVault() extension method to instruct ASP.NET to read secrets from Key Vault and override our application configuration with those values.

We can set the value of KEYVAULT_ENDPOINT in Web App's app settings:

Add setting

As an example, here's how to access the secrets as configuration values in a Razor page:

@page
@inject Microsoft.Extensions.Configuration.IConfiguration config

<!-- ... -->

<table class="table">
    <thead>
        <tr>
            <th>Name</th>
            <th>Value</th>
        </tr>
    </thead>
    <tbody>
        <tr>
            <td>MySecret</td>
            <td>@config["MySecret"]</td>
        </tr>
        <tr>
            <td>Secrets:NestedSecret</td>
            <td>@config["Secrets:NestedSecret"]</td>
        </tr>
    </tbody>
</table>

Note that for secrets that are nested, the Key Vault secret name is separated with -- instead of :. For instance, a configuration value named Secrets:NestedSecret would correspond to a Key Vault secret named Secrets--NestedSecret (see the Key Vault screenshot earlier).

Now if we deploy the application to our Azure Web App, we should see the secrets are being read from Key Vault.

ta da

Source code

https://github.com/anthonychu/aspnetcore-msi-keyvault

Deploy the fully working application from this article to an Azure account:

Deploy to Azure