5 Ways to Handle Application Configuration & Secrets With Azure 🔐

Handing application configuration can be tricky. Let's dive into 5 different ways we can manage application secrets, configuration, connection strings, etc when building an ASP NET Application on Azure.

Rahul Pulikkot Nath
Rahul Pulikkot Nath

Table of Contents

Handing application configuration can be tricky.

When working with .NET applications make sure to use the power of configuration and configuration providers to handle any kind of configuration. Don’t try and reinvent it by adding new abstractions and interfaces.

Use IConfiguration and built in providers to handle configuration.

Here are 5 ways you can handle application configuration when building .NET applications on the Azure Infrastructure.

Local Environment Configuration

Application development starts on your local machine. So let's start our configuration too there.

Configuration Values for application are best defined in the appsettings.json. When building application I define all possible configuration keys in the appsettings.json file.

Whether the value for the configuration is actually defined there or not is another thing - some times it is , most of the times it's not. However having all the possible keys in one place, helps understand the possible values that exists.

For demo purposes, in this example let's define a hierarchical structure of configuration values for us to configure using different mechanisms. Let's start with a section Demo which has 6 different keys under that.

All values are set to appsettings.json so that we know where the value is being set. We will override these values from different sources that we will see as we go through the different possible options of setting values.

"Demo": {
    "Key1": "appsettings.json",
    "Key2": "appsettings.json",
    "Key3": "appsettings.json",
    "Key4": "appsettings.json",
    "Key5": "appsettings.json",
    "ConnectionString": "appsettings.json"
  }

Environment Specific Configuration Values

ASP NET Core supports overriding appsettings file based on the environment that you are running on.

For example, when we run the application on our local computer, ASP NET Core treats it as Development environment. This is set through the launchsettings.json file in the project using the ASPNETCORE_ENVIRONMENT (or DOTNET_ENVIRONMENT) which is set to 'Development'

By default ASP NET Core also adds a appsettings.Development.json file which overrides configuration values (if specified) in the appsettings file.

"Demo": {
  "Key1": "appsettings development",
  "Key3": "appsettings development",
  "Key5": "appsettings development"
}

The above settings in appsettings.Development.json overrides values for Key1, Key3 and Key5 that are specified in appsettings.json file.

The default Configuration Providers are set up in such a way that the environment specific configuration files overrides the default appsettings.json configuration values.

Secrets Manager

The appsettings files are usually stored as part of the source code and available in your git repository. This makes it a bad place to store any kind of sensitive information.

To avoid having to commit configuration files along with sensitive information into the source code repository, you can use the Secrets Manager Feature available as part of ASP NET.

To add values to Secrets Manager in local Development environment, you can either use the UI (through Visual Studio) or using the command line tool.

Values defined in the Secrets Manager overrides those in the appsettings file (both global and environment specific ones).

The below value in secrets.json (as part of the Secrets Manager) overrides the value for 'ConnectionString'.

{
  "Demo": {
    "ConnectionString":  "Secrets Manager"
  }

}

Test Configuration Controller

Let's write a method to show how these configuration values are evaluated at runtime.

The below MyConfigController exposes all the configuration values under the Demo section. This gives the actual values that the application uses at run time.

NOTE: This Endpoint is for demo purposes only and do not add such an endpoint to your real-world application. It's a recipe for disaster.

[ApiController]
[Route("[controller]")]
public class MyConfigController: ControllerBase
{
    public MyConfigController(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    [HttpGet]
    public Dictionary<string,string> Get()
    {
        return Configuration
            .GetSection("Demo")
            .GetChildren()
            .ToDictionary(a => a.Key, a => a.Value);
    }
}

Running the application and executing the MyConfig endpoint returns the configuration values as expected. Key1, Key3 and Key5 are overridden by the 'appsettings development' values  and the ConnectionString by 'Secrets Manager' value. (as shown below in the Swagger response).

App Settings From Different Configuration Sources

Now that we have the sample application running, let's set up a build-deploy pipeline. This will allow us to automatically deploy the application to various environments (development, test, production etc).

If you are new to setting up DevOps build-deploy pipeline, check out my YouTube series which covers different application deployment scenarios. For this particular

1. Azure DevOps Variables

Azure DevOps allows setting and overriding variables inside of the build-release pipeline. When creating release pipeline, under the Variables section we can set environment specific configuration values.

For hierarchical values '.' (dot) is used as the separator. So in this case to set the configuration value for Key1 which is under the Demo section we can set the value in DevOps pipeline as 'Demo.Key1'.

The value can set for all releases or specific to the Environment (or Stage). This is set using the Scope option. In the below diagram, Development and Test stand for independent environments and Release stands for all releases.

Configuration value set in Azure DevOps Release Variables

In this case since this is a Web App deployment job, under the deployment task there is an option to enable 'File Transform & Variable Substitution' and specify the files that needs to be substituted. In this case appsettings.json.

Azure DevOps Web Application Transform for configuration

Variables defined in the release pipeline that match the names in the configuration file will be automatically replaced with the value based on the Stage/Environment they are set for.

2. Azure App Settings

Azure resources mostly have a Setting configuration where you can define application configuration.

Values set in the App Settings is usually made available as environment values when running in Azure infrastructure. In this case since we are deploying to an Azure Web App you can set this from the Azure Portal manually or also via the build/deploy pipeline.

Configuration From Azure App Settings in Web App Configuration

3. Azure Key Vault

Azure Key Vault is a cloud-hosted service for managing cryptographic keys and secrets like connection strings, API keys, and similar sensitive information. Key Vault provides centralized storage for application secrets. Check out my posts on Key Vault if you are new to Azure Key Vault and want to learn more.

You can integrate with Key Vault in two ways

  1. Deploy Time: In Azure DevOps you can create Variable Groups and directly integrate with Azure Key Vault and select specific secrets to be applied for your application.
  2. Runtime: Applications can also talk to Key Vault at runtime and retrieve values from it.  The application can pull down values on startup or refresh them periodically or fetch from Key Vault any time it needs it.
Connect .Net Core To Azure Key Vault In Ten Minutes
Access secrets in Azure Key Vault from .Net Core and learn how to elegantly handle when rotating secrets.

4. Azure App Configuration

Azure App Configuration provides a service to centrally manage application settings and feature flags. It allows to centralize application configuration to a single place and have different applications use it.

For an ASP NET application you can use the Microsoft.Azure.AppConfiguration.AspNetCore nuget package to integrate Azure App Configuration to the default Configuration provider. Below is a sample code on how you can do this.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder
            .ConfigureAppConfiguration(config =>
            {
                var settings = config.Build();
                var connection = settings.GetValue<string>("Demo:ConnectionString");
                config.AddAzureAppConfiguration(connection);
            })
        .UseStartup<Startup>();
    });

5. Managed Identity

Managed Identities provide an identity for applications, that can be used to authenticate resources in the Azure infrastructure.

Managed Identity removes the needs for connection strings, secrets etc to be configured explicitly. Application resources can be configured such that they can talk with each other and ensuring they are authenticated and authorized is managed by the Azure infrastructure.

In the past, Azure had different ways to authenticate with the various resources. The Azure SDK’s is bringing this all under one roof and providing a more unified approach to developers when connecting to resources on Azure.

For resources that support  Managed Identity, the client SDKs also take in the DefaultAzureCredential as part of their construction of initialization code.

DefaultAzureCredential: Unifying How We Get Azure AD Token
Azure Identity library provides Azure Active Directory token authentication support across the Azure SDK

The DefaultAzureCredential gets the token based on the environment the application is running. The following credential types if enabled will be tried, in order - EnvironmentCredential, ManagedIdentityCredential, SharedTokenCacheCredential, InteractiveBrowserCredential.

Below is a sample code that uses DefaultAzureCredential to connect to Azure App Configuration

Host.CreateDefaultBuilder(args)
  .ConfigureAppConfiguration(configuration => {
    configuration.AddAzureAppConfiguration(options => {
      options
        .Connect(new Uri("https://youtube-demo-test.azconfig.io"), new DefaultAzureCredential())
        .UseFeatureFlags();
    });
  })

As you can see above, it does not use any sensitive information to create the Azure App Configuration connection. All it uses is  the URL to the resource and the DefaultAzureCredential class, which does all the heavy lifting.

We have seen different options on how to store configuration values when building .NET applications that are running on Azure Infrastructure. Based on your team and application type you can pick and choose different options shown above.

Mix and match options if that is what works for you.

But always make sure that you store sensitve information secure and never to check them to source control.

Photo by Max Bender on Unsplash

AzureDotnet-Core