Daniel Wagner – .NET Software Entwicklung

Introduction

In software development, keeping sensitive information secure is crucial and should be a key knowlege for developers. The topic can be hard to navigate, especially for those new to the field. This document provides an overview of best practices for handling secrets in different staging environments.
The examples are based on a common setup using a web frontend built with Blazor and a backend API built with ASP.NET Core, both deployed to Azure using Entra External ID (CIAM) for authentication. The principles, however, are applicable to a wide range of applications and technologies.

What is a Secret?

A secret is any piece of information that, if exposed, could lead to unauthorized access to systems or data. For example:

  • API keys
  • Database connection strings with credentials
  • Encryption keys
  • All passwords
  • OAuth tokens
  • Certificates
  • Internal configuration values that could be used to gain access to resources, like IP addresses
  • Webhooks with integrated Authentication tokens

A secret should never be hardcoded in source code or stored in configuration files that are checked into source control. Instead, secrets should be stored securely and accessed at runtime through secure means.

Appsettings in .NET applications

The appsettings.json file is the central place for application configuration in .NET applications. This file contains various settings that the application will use at runtime, such as database connections, API keys, and other configuration values.
However, it is important to note that the appsettings.json file should not contain any secrets or sensitive information, as it is typically checked into source control and can be accessed by anyone with access to the repository.
The common way to work around this is to use placeholders or empty values in the appsettings.json file. During runtime, values are read from various sources, each of which has a different priority. The last source will override the previous ones, so the last source wins. You can also define your own custom configuration sources.

Example of an appsettings.json file with placeholders:

{
  "ConnectionStrings": {
    "DefaultConnection": ""
  },
  "ApiKeys": {
    "SomeService": ""
  }
}

The standard orders of configuration sources in .NET applications from lowest to highest priority are:

1. appsettings.json
The appsettings.json file has the lowest priority, meaning that if you define a value only in this file, it will be used.

2. appsettings.{Environment}.json
You can have environment-specific configuration files, e.g., appsettings.Development.json, appsettings.Production.json, etc. The environment is determined by the ASPNETCORE_ENVIRONMENT environment variable. Values in these files will override those in appsettings.json.

3. User Secrets (Development only)
The secrets file is a file typically stored in the user’s profile directory and is not checked into source control. It is used for development purposes to store secrets securely on the developer’s machine. Values defined here will override those in appsettings.json and appsettings.{Environment}.json.

4. Environment Variables
The next source of configuration is environment variables. These are set at the operating system level and are often used in production environments.

5. Command-line Arguments
Finally, command-line arguments have the highest priority and will override all other configuration sources. This is useful for temporary overrides or for running the application with specific settings without changing any files or environment variables.

Azure Key Vault and Managed Identity

For Azure-hosted applications, Azure Key Vault is the recommended service for storing and managing secrets. It provides secure storage for secrets, keys, and certificates, and allows you to control access to them using role-based access control (RBAC), ensuring the least privilege principle is followed.
It also provides built-in access monitoring for security hardening and auditing purposes. A recommended approach is to use one Key Vault per development environment (e.g., Dev, Test, Prod) to keep secrets organized. This approach also provides an additional layer of security by isolating secrets between environments. To access Key Vault secrets from your application, you must configure access in the appsettings.json file.

An example of how to configure access to Azure Key Vault in the appsettings.json:

var builder = WebApplication.CreateBuilder(args);

var keyVaultUri = builder.Configuration["KeyVaultUri"];
if (!string.IsNullOrEmpty(keyVaultUri))
{
    builder.Configuration.AddAzureKeyVault(new Uri(keyVaultUri), new DefaultAzureCredential());
}

This will automatically add Azure Key Vault as the source with the highest priority, so any secrets defined in the Key Vault will override those in any other source.

How does the application authenticate to Azure Key Vault without using another secret?
For azure services that communicate with each other, e.g. the backend API running in Azure App Services with the Key Vault, you can use a feature that’s called Managed Identity. Think of it as a service account for your application that can be assigned the same privileges as a standard user account. Another key part here is new DefaultAzureCredential() in the code snippet above. This is a class from the Azure Identity library that automatically detects the environment and uses the appropriate authentication method. For example, when running in Azure, it will use the Managed Identity of the application to authenticate to Azure Key Vault and for local development it will use the developer’s credentials (e.g., Azure CLI or Visual Studio credentials).
This allows the application to securely access Azure Key Vault without needing to store any secrets for authentication.

Azure Key Vault vs. the secret file in local development

For local development, you can use both solutions, the secret file or Azure Key Vault. The main advantage of using Key Vault is that you don’t have to share secrets manually within your team members. It also is a centralized service and acts as a single source of truth. Key Vaults provides better security overall, also when working with KI tools.
On the other hand, the secret file is easier to set up and doesn’t require internet access. In my opinion, Azure Key Vault is the better solution for big teams and projects, while the secret file can be a good option for small projects or individual developers.

The Environment variables in Azure App Services

Azure App Services provides a convenient way to manage environment variables through the Azure portal. You can set environment variables for your application in the „Configuration“ section of your App Service. But is is not recommended to use for storing secrets, as the values are stored in plain text and are visible to anyone with access to the Azure portal.
You should only use environment variables for non-sensitive configuration values, such as feature flags or application settings that do not contain secrets.

How to store the most common secrets in an example .NET application

1. Authentication Configuration

When using Azure Entra ID as the authentication provider, you must provide the following configuration values in the API and the Blazor frontend. Note, that you should never store secrets in an Single Page Application (SPA) like Blazor WebAssembly, as the code runs in the web browser and can be easily inspected by users.
Instead, you should use the Authorization Code Flow with PKCE, which allows you to authenticate users securely without needing to store any secrets in the frontend. Using an authentication provider like Azure Entra Id also has the advantage that you don’t have to store any sensitve data, like encryption keys or client secrets, in your application at all.

ParameterDescriptionSecret?
InstanceBase URL of the Entra External ID / CIAM tenant. For standard AAD: https://login.microsoftonline.com. For CIAM: https://<name>.ciamlogin.comNo
TenantIdGUID of the Azure AD tenant that manages the identitiesNo
ClientIdApp registration ID of the API in Entra. The API server uses this to verify whether incoming tokens were issued for this appNo
AudienceExpected recipient in the JWT token (aud claim). Usually identical to ClientIdNo

As you can see, there are no secrets required for the authentication. Azure knows that the front and backend are trusted applications as you have registered them in the same tenant and assigned the necessary permissions.

2. Database Connection

The database connection string is definitely a secret, as it often contains credentials that could be used to access the database. In development, you can use a connection string that relies on Windows Authentication (e.g., Trusted_Connection=True), which does not require storing it as secret.
In production, you should use either SQL Authentication (with a username and password) or Managed Identity. In both cases, the connection string should be stored securely, e.g., in Azure Key Vault.

ParameterDescriptionSecret?
AppTrackConnectionStringFull ADO.NET connection string to SQL Server. In production, it typically contains User ID and Password or a managed identity tokenYes (if credentials are included)

Development with LocalDB: Trusted_Connection=True uses Windows authentication – no password required, therefore no secret.

Production: Either SQL authentication (password = secret) or managed identity without a password.

Conclusion

Handling secrets can be a complex topic. By choosing modern services you can reduce the complexity significantly by reducing the number of secrets that you have to use and manage and by providing a central service that allows controlled access following the least privilege principle.


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert