RO EN

Azure Key Vault (2) — automatic rotation and certificates

Azure Key Vault (2) — automatic rotation and certificates
Doru Bulubașa
26 June 2026
35 views

The second part of the series about Azure Key Vault. In the first part we integrated Key Vault with IConfiguration. Here we deal with secret rotation and certificates.


Automatic secret rotation

Manual secret rotation is a fragile process: someone has to generate the new value, update it in Key Vault, propagate it to all systems using it, and ensure there is no downtime during the transition. Key Vault provides mechanisms to automate and simplify this process.

Expiration policies

# Set an expiration date on a secret
az keyvault secret set-attributes \
  --vault-name my-app-keyvault \
  --name "Stripe--ApiKey" \
  --expires "2026-01-01T00:00:00Z"

# Tag for notification 30 days before expiration
az keyvault secret set-attributes \
  --vault-name my-app-keyvault \
  --name "Stripe--ApiKey" \
  --tags "notify-days-before-expiry=30"

Automatic reload in ASP.NET Core

By default, AddAzureKeyVault loads secrets only once at startup. If you rotate a secret in Key Vault, the application will continue to use the old value until restarted. You can configure periodic reload:

builder.Configuration.AddAzureKeyVault(
    new Uri(kvUri),
    new DefaultAzureCredential(),
    new AzureKeyVaultConfigurationOptions
    {
        // Reload secrets every 30 minutes
        // Allows rotation without application restart
        ReloadInterval = TimeSpan.FromMinutes(30)
    });

With ReloadInterval set, the application automatically detects new versions. But it matters how you consume the configuration in services:

// IOptions -- value is fixed at startup, NOT updated
public StripeService(IOptions options) { ... }

// IOptionsSnapshot -- reloaded every request scope
// Compatible with ReloadInterval, but only in scoped/transient services
public StripeService(IOptionsSnapshot options) { ... }

// IOptionsMonitor -- value always current, safe even in singleton
public class StripeService
{
    private readonly IOptionsMonitor _monitor;

    public StripeService(IOptionsMonitor monitor)
    {
        _monitor = monitor;
        _monitor.OnChange(opts =>
            _logger.LogInformation("Stripe options updated"));
    }

    private string ApiKey => _monitor.CurrentValue.ApiKey;
}

Practical rule: use IOptionsMonitor<T> when the service is singleton (typical case for a reused HTTP client), IOptionsSnapshot<T> when the service is scoped per request.

Zero downtime rotation strategy

Zero downtime rotation requires a transition period during which both versions of the secret are valid. The recommended pattern:

  1. Generate the new API key at the provider (Stripe, SendGrid, etc.)
  2. Add the new version as a new secret in Key Vault (Key Vault keeps all versions)
  3. Wait for ReloadInterval to distribute the new value to all instances
  4. Disable the old version in Key Vault
  5. Revoke the old key at the provider
# Add new version (old version remains accessible)
az keyvault secret set \
  --vault-name my-app-keyvault \
  --name "Stripe--ApiKey" \
  --value "sk_live_new_value..."

# List versions of a secret
az keyvault secret list-versions \
  --vault-name my-app-keyvault \
  --name "Stripe--ApiKey"

# Disable an old version after transition
az keyvault secret set-attributes \
  --vault-name my-app-keyvault \
  --name "Stripe--ApiKey" \
  --version "old-version-id" \
  --enabled false

Certificates in Key Vault

Key Vault can also manage X.509 certificates — import, generation, automatic renewal, and distribution. Certificates stored in Key Vault are accessible to applications without copying them to disk.

Import existing certificate

az keyvault certificate import \
  --vault-name my-app-keyvault \
  --name "my-tls-cert" \
  --file "./certificate.pfx" \
  --password "pfx-password"

Generate certificate with automatic renewal

# Certificate policy with AutoRenew
cat > cert-policy.json << EOF
{
  "issuerParameters": { "name": "Self" },
  "keyProperties": { "keyType": "RSA", "keySize": 2048, "reuseKey": false },
  "secretProperties": { "contentType": "application/x-pkcs12" },
  "x509CertificateProperties": {
    "subject": "CN=my-app.azurewebsites.net",
    "validityInMonths": 12
  },
  "lifetimeActions": [{
    "action": { "actionType": "AutoRenew" },
    "trigger": { "daysBeforeExpiry": 30 }
  }]
}
EOF

az keyvault certificate create \
  --vault-name my-app-keyvault \
  --name "my-tls-cert" \
  --policy @cert-policy.json

The AutoRenew parameter in lifetimeActions causes Key Vault to automatically renew the certificate 30 days before expiration. No more surprise expired certificates.

Access certificates from .NET code

dotnet add package Azure.Security.KeyVault.Certificates
public class CertificateService
{
    private readonly Uri _kvUri;

    public CertificateService(IConfiguration config)
        => _kvUri = new Uri(config["KeyVaultUri"]!);

    public async Task GetCertificateAsync(string certName)
    {
        // Certificates are also stored as secrets in Key Vault
        // (PFX format is accessible via SecretClient)
        var secretClient = new SecretClient(_kvUri, new DefaultAzureCredential());
        var secret = await secretClient.GetSecretAsync(certName);

        var certBytes = Convert.FromBase64String(secret.Value.Value);
        return new X509Certificate2(certBytes, (string?)null,
            X509KeyStorageFlags.MachineKeySet |
            X509KeyStorageFlags.PersistKeySet |
            X509KeyStorageFlags.Exportable);
    }
}

Certificate for mTLS or client credentials

// HttpClient with client certificate from Key Vault
builder.Services.AddHttpClient("secure-api")
    .ConfigurePrimaryHttpMessageHandler(sp =>
    {
        var certService = sp.GetRequiredService

What’s next

In the third part we cover granular access via RBAC at the individual secret level (the least privilege principle), full audit trail in Log Analytics, and Key Vault References in App Service — plus the complete pattern for configuration without any hardcoded secrets.

Questions? Write to me at contact@ludoprogramming.com.