In a production multi-instance deployment, all application servers must share the same Data Protection key ring โ if Server A encrypts a session cookie and Server B cannot decrypt it, users are randomly logged out depending on which server their request hits. Key management โ where keys are stored, how they are encrypted at rest, how long they live, and how they are rotated โ is an operational security concern that must be planned before going to production. This lesson covers the key storage and rotation patterns used in cloud-native ASP.NET Core deployments.
Key Ring Configuration for Production
// โโ Production key storage โ Azure Blob + Key Vault โโโโโโโโโโโโโโโโโโโโโโโ
// Keys XML file stored in Azure Blob Storage (all instances read/write here)
// Keys encrypted with Azure Key Vault (key encryption key โ never leaves vault)
builder.Services.AddDataProtection()
.SetApplicationName("BlogApp") // must match across all services
.SetDefaultKeyLifetime(TimeSpan.FromDays(90))
.PersistKeysToAzureBlobStorage(
new Uri(builder.Configuration["DataProtection:BlobUri"]!),
new DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(
new Uri(builder.Configuration["DataProtection:KeyVaultKeyUri"]!),
new DefaultAzureCredential());
// โโ What this achieves: โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
// - All 10 pods read the SAME key XML from Blob Storage
// - The key XML is encrypted with a Key Vault key (server never sees raw key material)
// - Key rotation: every 90 days a new key is added; old keys kept for decryption
// - Key Vault manages the master key; audits all key operations
// - Managed Identity authentication โ no stored credentials
SetApplicationName("BlogApp") call is essential when multiple different applications share the same key storage location. Without it, application names default to the application’s base path, which varies between deployments. Two applications with different names cannot decrypt each other’s payloads even if they share the storage. Set a consistent, stable application name and store it in configuration to ensure it is the same across all environments and deployments.SetApplicationName value in a running production system without a coordinated key migration. Changing the application name invalidates all existing protected payloads โ every session cookie, anti-forgery token, and password reset token becomes undecryptable. All users are logged out simultaneously. If you must rename the application, schedule a maintenance window, update the name, and inform users that they will need to log in again.Key Rotation and Auditing
// โโ Key rotation โ automatic, transparent to the application โโโโโโโโโโโโโโ
// When a key approaches expiry (within 5 days of 90-day lifetime by default),
// Data Protection automatically creates a new key. New payloads are protected
// with the new key. Old payloads protected by the expiring key can still be
// decrypted until the key is revoked or deleted.
// โโ IKeyEscrowSink โ audit all key creation events โโโโโโโโโโโโโโโโโโโโโโโโ
public class KeyAuditSink(ILogger<KeyAuditSink> logger) : IKeyEscrowSink
{
public void Store(Guid keyId, XElement element)
{
// Log key creation for compliance auditing
// DO NOT log the actual key material (element contains it)
logger.LogInformation(
"New data protection key created. KeyId={KeyId} CreatedAt={CreatedAt}",
keyId, DateTime.UtcNow);
}
}
builder.Services.AddDataProtection()
.SetApplicationName("BlogApp")
.AddKeyEscrowSink<KeyAuditSink>(); // register audit sink
// โโ Viewing active keys (for operational visibility) โโโโโโโโโโโโโโโโโโโโโโ
// IKeyManager can be injected to inspect the key ring:
public class KeyRingController(IKeyManager keyManager) : ControllerBase
{
[HttpGet("/admin/keys"), Authorize(Roles = "Admin")]
public IActionResult GetKeyInfo()
{
var keys = keyManager.GetAllKeys().Select(k => new
{
k.KeyId,
k.CreationDate,
k.ExpirationDate,
k.IsRevoked,
});
return Ok(keys);
}
}
Common Mistakes
Mistake 1 โ Using in-memory key storage in production (lost on restart)
โ Wrong โ default in-memory key ring is lost on pod restart; all protected data becomes unreadable:
builder.Services.AddDataProtection(); // no PersistKeysTo* call โ in-memory only!
โ Correct โ always configure a persistent key storage provider in production.
Mistake 2 โ Changing SetApplicationName on a live system (invalidates all sessions)
โ Wrong โ renaming silently logs out all users and invalidates all tokens.
โ Correct โ treat the application name as immutable once in production; plan any changes as a coordinated migration.