Secret Versioning & Rotation
Overview
Azure Key Vault automatically versions secrets on every update. Combined with rotation strategies and expiry notifications, you can build a fully automated secret lifecycle without application downtime.
How Versioning Works
Every time a secret is set (created or updated), Key Vault creates a new version identified by a unique GUID. The secret name remains the same.
https://my-vault.vault.azure.net/secrets/DbPassword
→ latest version (always returned by default)
https://my-vault.vault.azure.net/secrets/DbPassword/abc123def456
→ specific version
Key Behaviors
- Latest version is returned when no version is specified in the URI.
- Old versions remain accessible by their version ID until explicitly disabled or deleted.
- Soft delete protects against accidental deletion (enabled by default on new vaults).
Listing Secret Versions
Azure CLI
az keyvault secret list-versions \
--vault-name my-vault \
--name DbPassword \
--query "[].{id:id, created:attributes.created, enabled:attributes.enabled}" \
-o table
.NET SDK
var client = new SecretClient(new Uri(vaultUrl), new DefaultAzureCredential());
await foreach (SecretProperties version in client.GetPropertiesOfSecretVersionsAsync("DbPassword"))
{
Console.WriteLine($"Version: {version.Version}, Created: {version.CreatedOn}, Enabled: {version.Enabled}");
}
Setting Expiry on Secrets
Secrets can have an expiration date. Key Vault does not automatically delete expired secrets, but it emits events and marks them as expired.
# Linux/Cloud Shell:
az keyvault secret set \
--vault-name my-vault \
--name ApiKey \
--value "my-secret-value" \
--expires "$(date -u -d '+90 days' +%Y-%m-%dT%H:%M:%SZ)"
# macOS:
az keyvault secret set \
--vault-name my-vault \
--name ApiKey \
--value "my-secret-value" \
--expires "$(date -u -v+90d +%Y-%m-%dT%H:%M:%SZ)"
.NET SDK
var secret = new KeyVaultSecret("ApiKey", "my-secret-value");
secret.Properties.ExpiresOn = DateTimeOffset.UtcNow.AddDays(90);
await client.SetSecretAsync(secret);
Expiry Notifications with Event Grid
Key Vault emits events when secrets are near expiry:
| Event | Trigger |
|---|---|
Microsoft.KeyVault.SecretNearExpiry | 30 days before expiry (fixed) |
Microsoft.KeyVault.SecretExpired | On expiration date |
Microsoft.KeyVault.SecretNewVersionCreated | When a new version is created |
Subscribe to Events
az eventgrid event-subscription create \
--name secret-expiry-alert \
--source-resource-id /subscriptions/{sub}/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-vault \
--endpoint https://my-function.azurewebsites.net/api/SecretExpiryHandler \
--included-event-types Microsoft.KeyVault.SecretNearExpiry
Automated Rotation Strategies
Strategy 1: Event-Driven Rotation (Recommended)
- Set secret expiry (e.g., 90 days).
- Subscribe to
SecretNearExpiryevent. - Azure Function regenerates the credential at the source.
- Function stores the new value in Key Vault.
- Applications using latest version pick up the change automatically.
Rotation Function Example (.NET)
[Function("RotateStorageKey")]
public async Task Run([EventGridTrigger] EventGridEvent eventGridEvent)
{
var secretName = eventGridEvent.Subject;
// 1. Regenerate key at the source
var newKey = await RegenerateStorageKeyAsync();
// 2. Store new version in Key Vault
var client = new SecretClient(new Uri(vaultUrl), new DefaultAzureCredential());
var secret = new KeyVaultSecret(secretName, newKey);
secret.Properties.ExpiresOn = DateTimeOffset.UtcNow.AddDays(90);
await client.SetSecretAsync(secret);
}
Strategy 2: Scheduled Rotation
Use a Timer-triggered Azure Function to rotate secrets on a fixed schedule:
[Function("ScheduledRotation")]
public async Task Run([TimerTrigger("0 0 0 1 */2 *")] TimerInfo timer)
{
// Runs on the 1st of every 2nd month
await RotateSecretAsync("DbPassword");
}
Dual-Version Rotation Pattern
For zero-downtime rotation of credentials that require propagation time:
- Primary secret is active (version N).
- Rotation creates secondary (version N+1) and activates it at the source.
- After propagation delay, disable version N.
- Applications always fetch the latest version.
// Disable old version after rotation
SecretProperties oldVersion = await client.GetSecretAsync("DbPassword", oldVersionId);
oldVersion.Enabled = false;
await client.UpdateSecretPropertiesAsync(oldVersion);
Application-Side Caching
Applications should cache secrets with a reasonable TTL and refresh periodically:
public class SecretCache
{
private readonly SecretClient _client;
private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(30);
private string _cachedValue;
private DateTimeOffset _cacheExpiry;
public async Task<string> GetSecretAsync(string name)
{
if (_cachedValue != null && DateTimeOffset.UtcNow < _cacheExpiry)
return _cachedValue;
var secret = await _client.GetSecretAsync(name);
_cachedValue = secret.Value.Value;
_cacheExpiry = DateTimeOffset.UtcNow.Add(_cacheDuration);
return _cachedValue;
}
}
Best Practices
- Always set expiry dates on secrets to enforce rotation.
- Use Event Grid for near-expiry notifications rather than manual tracking.
- Automate rotation with Azure Functions — never rotate manually in production.
- Implement dual-version pattern for credentials that need propagation time.
- Cache secrets in applications with a 15-30 minute TTL to reduce Key Vault calls.
- Disable old versions after successful rotation rather than deleting immediately.
- Enable soft delete and purge protection to prevent accidental permanent loss.
- Monitor rotation success/failure with Application Insights alerts.
Summary
Secret versioning is automatic in Key Vault. Combine it with expiry dates, Event Grid notifications, and automated rotation functions to build a self-healing secret lifecycle. Applications that always fetch the latest version benefit from seamless rotation without redeployment.