Applications that run across Azure, AWS, Google Cloud, or private infrastructure often need a consistent way to access secrets.

A common .NET application might use Azure Key Vault when deployed to Azure, while the same application needs another secret provider when deployed to a different cloud. Problems usually appear when the application configuration is tightly coupled to one cloud provider.

The goal is not to make every cloud behave identically. The goal is to create a clear configuration boundary so that the application can retrieve secrets without knowing where those secrets are physically stored.

This article explains how to structure Azure Key Vault secret provider configuration for multi-cloud .NET deployments, how to separate provider configuration from application settings, and how to avoid common authentication and configuration problems.

Why Multi-Cloud Secret Configuration Needs a Clear Boundary

Consider an application deployed in three environments:

Code
Production Azure
      |
      v
Azure Key Vault

Production AWS
      |
      v
AWS Secret Manager

Private Environment
      |
      v
Internal Secret Store

The application still needs configuration such as:

Code
Database connection string
API credentials
Encryption keys
Third-party service credentials

If application code directly calls Azure Key Vault everywhere, moving that application to another environment becomes difficult.

A better architecture is:

Code
Application
     |
     v
Secret Provider Interface
     |
     +---- Azure Key Vault
     |
     +---- AWS Secret Manager
     |
     +---- Other provider

The application depends on the interface rather than the provider.

Keep Application Configuration Provider-Agnostic

A configuration file should describe which provider is active rather than embedding provider-specific behavior throughout the application.

For example:

JSON
{
  "Secrets": {
    "Provider": "AzureKeyVault"
  }
}

For another deployment:

JSON
{
  "Secrets": {
    "Provider": "AwsSecretsManager"
  }
}

The application can then select the implementation during startup.

The important point is that business services should not contain logic such as:

Code
If Azure:
    call Key Vault

If AWS:
    call Secrets Manager

If private:
    call another provider

That logic belongs in the infrastructure configuration layer.

Use Azure Key Vault Through the Configuration System

For an Azure-hosted .NET application, Azure Key Vault can be integrated into the .NET configuration pipeline.

A typical setup uses Azure.Identity for authentication and the Azure Key Vault configuration provider.

The application can load normal configuration first:

C#
var builder = WebApplication.CreateBuilder(args);

Then add the Key Vault configuration source using the appropriate Azure Key Vault endpoint and credential.

Conceptually:

C#
builder.Configuration.AddAzureKeyVault(
    keyVaultUri,
    credential,
    new AzureKeyVaultConfigurationOptions());

The exact provider package and configuration options should match the .NET version and Azure SDK packages used by the application.

The benefit of using the configuration provider is that application services can read configuration through the standard .NET configuration system instead of directly calling Key Vault for every value.

Use DefaultAzureCredential

For modern Azure applications, authentication should be separated from application code.

A common approach is:

C#
using Azure.Identity;

var credential = new DefaultAzureCredential();

This allows the application to use supported identity sources based on its execution environment.

For example:

Code
Local Development
       |
       v
Developer Identity

Azure Hosting
       |
       v
Managed Identity

This avoids storing a Key Vault client secret inside the application.

For production workloads, managed identity is generally preferable when the hosting service supports it.

Give the Application Only Required Permissions

The application identity should not receive unrestricted access to the vault.

If an application only needs to read secrets, its identity should receive the minimum required secret permissions.

The principle is:

Code
Application
    |
    v
Managed Identity
    |
    v
Key Vault
    |
    +--> Read required secrets

It should not automatically receive permissions to create, delete, or modify secrets.

Least privilege becomes even more important in multi-cloud environments because a compromised application identity can otherwise become a path to additional infrastructure.

Separate Secret Names From Secret Locations

A useful design is to keep the application focused on logical configuration names.

For example:

Code
Database:ConnectionString
Payment:ApiKey
Storage:ConnectionString

The application should not need to know whether the value comes from:

Code
Azure Key Vault
AWS Secrets Manager
Environment variable
Private secret store

This creates a clean abstraction:

Code
Logical Secret
      |
      v
Provider
      |
      v
Actual Secret Storage

This approach also makes testing easier.

Designing a Secret Provider Interface

A .NET application can define a small abstraction:

C#
public interface ISecretProvider
{
    Task<string?> GetSecretAsync(
        string name,
        CancellationToken cancellationToken);
}

An Azure implementation can use Key Vault:

C#
public sealed class AzureKeyVaultSecretProvider
    : ISecretProvider
{
    private readonly SecretClient _client;

    public AzureKeyVaultSecretProvider(SecretClient client)
    {
        _client = client;
    }

    public async Task<string?> GetSecretAsync(
        string name,
        CancellationToken cancellationToken)
    {
        var response =
            await _client.GetSecretAsync(
                name,
                cancellationToken: cancellationToken);

        return response.Value.Value;
    }
}

A different provider can implement the same interface.

The rest of the application does not need to change.

Configuration Provider vs Direct Secret Provider

There are two different patterns, and they should not be mixed without a reason.

Configuration provider

Use this when secrets should behave like normal application configuration.

Code
Key Vault
   |
   v
.NET Configuration
   |
   v
Application Options

For example:

C#
var connectionString =
    configuration["Database:ConnectionString"];

Direct secret provider

Use this when the application needs to retrieve secrets dynamically or selectively.

Code
Application
    |
    v
ISecretProvider
    |
    v
Key Vault

For example:

C#
var apiKey =
    await secretProvider.GetSecretAsync(
        "payment-api-key",
        cancellationToken);

The configuration-provider approach is usually cleaner for application startup configuration. Direct retrieval can be useful for dynamic or on-demand secret access.

Selecting a Provider Per Environment

The provider selection can be controlled through environment configuration.

For example:

JSON
{
  "Secrets": {
    "Provider": "AzureKeyVault"
  }
}

Development might use:

JSON
{
  "Secrets": {
    "Provider": "Development"
  }
}

A private environment could use:

JSON
{
  "Secrets": {
    "Provider": "PrivateVault"
  }
}

The application startup code can then register the appropriate provider.

Conceptually:

C#
var providerName =
    builder.Configuration["Secrets:Provider"];

if (providerName == "AzureKeyVault")
{
    // Register Azure provider
}
else if (providerName == "Development")
{
    // Register development provider
}

For larger applications, dependency injection registration can be organized into separate extension methods rather than keeping a large conditional block in Program.cs.

Avoid Storing Secrets in Configuration Files

A configuration file can contain the location or provider configuration, but actual credentials should not be committed to source control.

Avoid:

JSON
{
  "PaymentApiKey": "actual-secret-value"
}

Instead, store the secret in the appropriate secret store.

For local development, developers can use environment-specific secret mechanisms or the .NET user secrets feature where appropriate.

The principle is simple:

Code
Configuration:
Where and how to retrieve the secret

Secret Store:
Actual secret value

Handling Different Cloud Environments

A multi-cloud application can use a deployment-specific configuration layer.

For example:

Code
                     Application
                          |
                          v
                  ISecretProvider
                          |
          +---------------+---------------+
          |               |               |
          v               v               v
     Azure Key Vault   AWS Secret      Private Vault
                       Manager

The application code remains unchanged.

Only the infrastructure registration changes.

This is particularly useful for applications that are packaged once and deployed into multiple environments.

Keep Azure-Specific Code in Infrastructure Projects

If the application is large, consider separating infrastructure integrations.

For example:

Code
src/
    Application/
        Services/
        Interfaces/

    Infrastructure/
        Azure/
            AzureKeyVaultSecretProvider.cs

        Aws/
            AwsSecretProvider.cs

        Private/
            PrivateSecretProvider.cs

The application project can depend on the abstraction:

Code
Application
    |
    v
ISecretProvider

The infrastructure project provides the implementation.

This keeps cloud-specific dependencies from spreading across business logic.

Secret Rotation

Secret rotation is another reason to avoid hard-coded values.

Suppose a database password changes:

Code
Old Secret
    |
    v
Key Vault

Application
    |
    v
Reads current secret

The application should have a clear strategy for handling rotation.

For secrets loaded only during application startup, rotation may require application restart or configuration refresh.

For dynamically retrieved secrets, the application may fetch the current value when needed.

Do not assume that adding Key Vault automatically makes every application secret rotation-aware.

The refresh behavior depends on how the application consumes the configuration.

Multi-Cloud Configuration Example

A deployment configuration might look like this:

JSON
{
  "Secrets": {
    "Provider": "AzureKeyVault",
    "VaultUri": "configured-by-environment"
  }
}

The same application package could run elsewhere with:

JSON
{
  "Secrets": {
    "Provider": "AwsSecretsManager"
  }
}

The application services still request the same logical configuration.

This reduces cloud-specific changes during deployment.

Common Mistakes

Hard-coding Azure Key Vault calls everywhere

This makes the application difficult to move to another environment.

Keep Key Vault access behind a provider boundary.

Putting secrets in appsettings.json

Configuration files are not a secure secret store.

Store actual credentials in an appropriate secret-management system.

Giving excessive Key Vault permissions

The application should receive only the permissions it needs.

Using client secrets unnecessarily

Use managed identity or another supported identity mechanism where appropriate.

Confusing configuration refresh with secret rotation

A changed Key Vault value does not automatically mean every running application instance immediately sees the new value.

Mixing cloud logic with business logic

Business services should not contain Azure, AWS, or other provider-specific code.

Creating one giant secret provider abstraction

Keep the abstraction small and focused. It should expose only the operations the application actually needs.

Troubleshooting

Problem Possible Cause What to Check
Application cannot read Key Vault Identity lacks permission Managed identity and Key Vault access
Authentication fails locally Developer identity unavailable Local Azure authentication
Secret is missing Wrong secret name Key Vault secret naming and configuration mapping
Application still uses old secret Configuration was loaded only at startup Refresh and restart behavior
Multi-cloud deployment fails Provider registration is environment-specific Provider configuration
Business code contains Azure SDK calls Missing abstraction Introduce ISecretProvider
Secret appears in logs Unsafe logging Remove secret values from logs

Best Practices

  • Keep cloud-specific secret access behind an abstraction.
  • Use Azure Key Vault for Azure-hosted workloads when appropriate.
  • Prefer managed identity for supported Azure hosting environments.
  • Apply least-privilege permissions.
  • Keep actual secret values outside source-controlled configuration.
  • Separate logical secret names from physical secret locations.
  • Select providers through environment configuration.
  • Keep Azure-specific implementation code in the infrastructure layer.
  • Decide whether each secret needs startup configuration or dynamic retrieval.
  • Design secret rotation behavior explicitly.
  • Never log secret values.
  • Test each deployment environment using the same application behavior.

Advantages and Disadvantages

Advantages Disadvantages
Reduces cloud-provider coupling Requires an abstraction layer
Centralizes secret access patterns More infrastructure configuration
Supports Azure and non-Azure deployments Different providers may have different capabilities
Works well with dependency injection Secret refresh behavior needs careful design
Improves security compared with hard-coded secrets Identity and permissions must be configured correctly
Makes cloud migration easier Testing multiple providers adds work

When This Approach Makes Sense

This architecture is useful when the same .NET application needs to run across multiple cloud environments or when the organization wants to keep cloud-specific infrastructure code separate from business logic.

It is especially useful for applications that may move between Azure and other environments over time.

For a small application that will permanently run in Azure, directly integrating Azure Key Vault through the standard .NET configuration pipeline may be simpler.

The key is to avoid introducing abstraction solely for theoretical portability. Add the provider boundary when the application actually has multiple deployment environments or a realistic requirement for provider independence.

Summary

Azure Key Vault works well as a secret provider for .NET applications, but multi-cloud deployments require a clear separation between application configuration and cloud-specific secret storage.

The application should work with logical configuration values, while the infrastructure layer decides whether those values come from Azure Key Vault, another cloud provider, or a private secret store.

For Azure deployments, use supported Azure identity mechanisms and apply least-privilege permissions. Keep actual secrets out of source-controlled configuration and define how secret rotation and configuration refresh will work.

A provider abstraction is most useful when the application genuinely needs multiple secret backends. With that boundary in place, the same .NET application can move between environments without spreading Azure-specific secret-management code throughout the application.