Learnixo
Back to blog
Backend Systemsintermediate

Azure Key Vault in .NET — Secrets and Managed Identity

Integrate Azure Key Vault with ASP.NET Core: add Key Vault as a configuration provider, authenticate with Managed Identity (no credentials in code), rotate secrets, and use DefaultAzureCredential.

Asma Hafeez KhanMay 24, 20264 min read
.NETC#AzureKey VaultsecurityManaged IdentityASP.NET Core
Share:𝕏

Azure Key Vault in .NET — Secrets and Managed Identity

Hardcoded secrets and connection strings in configuration files are a security risk. Azure Key Vault stores secrets centrally, and Managed Identity eliminates the need for credentials entirely.


Why Key Vault?

Without Key Vault:
  appsettings.Production.json:
    "ConnectionStrings": { "Default": "Server=prod;Password=SuperSecret123" }
  → Password in source control, visible in build artifacts, rotated manually

With Key Vault + Managed Identity:
  No password in any file
  No service principal credentials
  Secrets rotate without redeploying the app
  Access controlled by Azure RBAC

Step 1: Install Packages

XML
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.*" />
<PackageReference Include="Azure.Identity" Version="1.*" />

Step 2: Add Key Vault as a Configuration Provider

C#
// Program.cs — add Key Vault before builder.Build()
var keyVaultUri = builder.Configuration["KeyVault:Uri"];

if (!string.IsNullOrEmpty(keyVaultUri))
{
    builder.Configuration.AddAzureKeyVault(
        new Uri(keyVaultUri),
        new DefaultAzureCredential());   // uses Managed Identity in Azure, local credential chain in dev
}

var app = builder.Build();
JSON
// appsettings.json — only the vault URI, no secrets
{
  "KeyVault": {
    "Uri": "https://my-app-vault.vault.azure.net/"
  }
}

// appsettings.Development.json — local dev overrides (no vault needed locally)
{
  "ConnectionStrings": {
    "Default": "Host=localhost;Database=myapp;Username=dev;Password=dev"
  }
}
Key Vault secret naming:
  ASP.NET Core maps -- to : in secret names
  Secret name in Key Vault: ConnectionStrings--Default
  Becomes: ConnectionStrings:Default in IConfiguration
  Access: builder.Configuration.GetConnectionString("Default")

Step 3: DefaultAzureCredential — How Authentication Works

DefaultAzureCredential tries credential providers in order:
  1. EnvironmentCredential — AZURE_CLIENT_ID, AZURE_CLIENT_SECRET env vars
  2. WorkloadIdentityCredential — AKS with workload identity
  3. ManagedIdentityCredential — Azure VM, App Service, Container App, AKS node MSI
  4. SharedTokenCacheCredential — cached login from Visual Studio, VS Code
  5. VisualStudioCredential — az login in VS
  6. AzureCliCredential — az login in terminal
  7. AzurePowerShellCredential — Connect-AzAccount

In Azure (production): Managed Identity is used — no credentials, no rotation
In local dev: Azure CLI or Visual Studio signed-in account
C#
// Use DefaultAzureCredential in your own Azure SDK calls too
var secretClient = new SecretClient(
    new Uri("https://my-app-vault.vault.azure.net/"),
    new DefaultAzureCredential());

var secret = await secretClient.GetSecretAsync("MySecret");
Console.WriteLine(secret.Value.Value);

Step 4: Managed Identity Setup in Azure

Bash
# Enable system-assigned managed identity on App Service
az webapp identity assign \
  --name my-app \
  --resource-group my-rg

# Grant the identity access to Key Vault
az keyvault set-policy \
  --name my-app-vault \
  --object-id <identity-principal-id> \
  --secret-permissions get list

# Modern RBAC approach (preferred over access policies)
az role assignment create \
  --role "Key Vault Secrets User" \
  --assignee <identity-principal-id> \
  --scope /subscriptions/.../resourceGroups/.../providers/Microsoft.KeyVault/vaults/my-app-vault
YAML
# Kubernetes: use workload identity instead of node MSI
# Annotate the service account
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app-sa
  annotations:
    azure.workload.identity/client-id: "<managed-identity-client-id>"

Step 5: Reload Secrets Without Restart

C#
// By default, Key Vault secrets are loaded once at startup
// To reload secrets periodically (e.g., after rotation):
builder.Configuration.AddAzureKeyVault(
    new Uri(keyVaultUri),
    new DefaultAzureCredential(),
    new AzureKeyVaultConfigurationOptions
    {
        ReloadInterval = TimeSpan.FromMinutes(30),   // reload every 30 minutes
    });
C#
// Trigger reload in response to an event (e.g., rotation webhook):
public class SecretRotationController(IConfigurationRoot config) : ControllerBase
{
    [HttpPost("rotate")]
    [Authorize(Policy = "InternalOnly")]
    public IActionResult NotifyRotation()
    {
        config.Reload();   // forces Key Vault provider to refresh
        return Ok();
    }
}

Accessing Secrets Directly (Not via IConfiguration)

C#
// For secrets that are not connection strings (e.g., API keys used in one service)
public class PaymentService(SecretClient secretClient)
{
    private string? _apiKey;

    public async Task<string> GetApiKeyAsync(CancellationToken ct)
    {
        if (_apiKey is not null) return _apiKey;

        var secret = await secretClient.GetSecretAsync("PaymentGateway-ApiKey", cancellationToken: ct);
        _apiKey = secret.Value.Value;
        return _apiKey;
    }
}

// Register SecretClient in DI
builder.Services.AddSingleton(sp =>
    new SecretClient(
        new Uri(builder.Configuration["KeyVault:Uri"]!),
        new DefaultAzureCredential()));

Local Development — No Azure Required

C#
// Option 1: Use user secrets (dotnet user-secrets)
// Stored in ~/.microsoft/usersecrets/<project-id>/secrets.json
// Never in source control

dotnet user-secrets set "ConnectionStrings:Default" "Host=localhost;Database=myapp"
dotnet user-secrets set "PaymentGateway:ApiKey" "test-key-abc"

// Access: builder.Configuration["ConnectionStrings:Default"] — same code

// Option 2: Conditional vault usage
if (builder.Environment.IsProduction() || builder.Environment.IsStaging())
{
    builder.Configuration.AddAzureKeyVault(
        new Uri(builder.Configuration["KeyVault:Uri"]!),
        new DefaultAzureCredential());
}
// In dev: appsettings.Development.json + user-secrets take precedence

Interview Answer

"Azure Key Vault is added as an ASP.NET Core configuration provider with AddAzureKeyVault — it populates IConfiguration like any other source, so existing code reading configuration["ConnectionStrings:Default"] works unchanged; Key Vault secret names use -- as a separator (ConnectionStrings--Default becomes ConnectionStrings:Default). Authentication uses DefaultAzureCredential — in Azure it picks up the Managed Identity automatically, no credentials or service principal secrets required. Locally it falls back to az login or Visual Studio sign-in. Managed Identity requires assigning the Key Vault Secrets User RBAC role to the application's identity on the vault. Secrets are loaded at startup by default; set ReloadInterval to refresh them periodically after rotation without restarting. For local development, combine user-secrets (never committed) with conditional vault registration so the app works without Azure access."

Enjoyed this article?

Explore the Backend Systems learning path for more.

Found this helpful?

Share:𝕏

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.