Data Protection API
Key Points
- DPAPI = ASP.NET Core's symmetric encryption service for protecting data at rest in cookies, anti-forgery tokens, password reset tokens, etc. Not the Windows DPAPI.
- Keys are auto-rotated (default 90 days) and stored in a key ring. Old keys retained for decryption.
- In a multi-instance deployment, share the key ring — same Azure Key Vault / Redis / shared filesystem. Otherwise, each instance has its own keys; cookies/tokens from one don't decrypt on another.
- Use
IDataProtectionProviderto encrypt your own custom data: API keys, queue tokens, sensitive cache entries. - Don't use it for: long-term storage (you can lose keys), passwords (hash, don't encrypt), public-facing crypto (use proper KMS).
Concepts (deep dive)
What it does
Encrypts and authenticates short-lived secrets:
public class TokenIssuer(IDataProtectionProvider dp)
{
private readonly IDataProtector _protector = dp.CreateProtector("MyApp.Tokens.v1");
public string Issue(string payload) => _protector.Protect(payload);
public string Read(string token) => _protector.Unprotect(token);
}
Protect = encrypt + MAC. Unprotect = verify MAC + decrypt. Tampering = CryptographicException.
The key ring
DPAPI manages a key ring of versioned keys. New data uses the active key; old data validates against retired keys. Keys auto-rotate at 90 days; old keys stay until their tokens expire (default 90 + 7 days grace).
KeyRing
├── key_2025_11_01 (active)
├── key_2025_08_03 (retired but valid)
└── key_2025_05_05 (expired)
Storage backends: - File system (default in dev: %LocalAppData%\ASP.NET\DataProtection-Keys) - Azure Blob / Key Vault - Redis - Entity Framework - Custom (IXmlRepository)
Multi-instance deployment
Two web servers behind a load balancer:
- Server A issues a cookie with key
K1→ user lands on Server B. - Server B has its own keys
{K2, K3}— can't decrypt → user logged out.
Fix: shared key store + shared application name.
builder.Services.AddDataProtection()
.SetApplicationName("MyApp") // CRITICAL — same on all instances
.PersistKeysToAzureBlobStorage(connStr, container, blob)
.ProtectKeysWithAzureKeyVault(keyVaultUri, credential);
SetApplicationName is the discriminator — same name = same key ring.
Time-limited protectors
var protector = dp.CreateProtector("MyApp.PasswordReset")
.ToTimeLimitedDataProtector();
string token = protector.Protect(userId.ToString(), TimeSpan.FromHours(1));
// Later:
string user = protector.Unprotect(token, out var expirationUtc); // throws if expired
Built-in expiration. Don't roll your own.
Use cases
✅ Good fits: - Anti-forgery tokens - Auth cookie payloads - Password reset / email confirmation tokens - API keys you generate for users (short-lived) - Sensitive query string parameters - Encrypted cache entries
❌ Bad fits: - Passwords (hash with PBKDF2/Argon2id) - Long-term archive (key loss = data loss) - Public-facing encrypted blobs (use Key Vault / KMS) - Inter-service messaging (use proper key exchange)
Key escrow / rotation
If you need to read old data with a new deployment, back up the key ring. Losing keys = losing access to all data they protected.
In Azure, persisting to Key Vault gives you HSM-backed protection of the keys themselves (so the key ring is encrypted by Key Vault).
Purpose strings
dp.CreateProtector("MyApp.Tokens.v1"); // namespace
dp.CreateProtector("MyApp.Tokens.v1", "user-123"); // sub-purpose
Different purposes = cryptographically isolated. A token issued under Tokens.v1 won't decrypt under Tokens.v2. This prevents accidental cross-context use (e.g., a password-reset token being valid as an auth cookie).
Anti-forgery integration
ASP.NET's anti-forgery middleware uses DPAPI internally. The token includes user ID + sequence + a secret protected by your key ring. When you [ValidateAntiForgeryToken], the middleware decrypts and verifies.
Configuring lifetime
Shorter = more frequent rotation = smaller blast radius if a key leaks. But you must persist enough history.
Cross-environment isolation
Different names per environment = isolated key rings. A staging cookie won't decrypt in production.
Code: correct vs wrong
❌ Wrong: rolling AES manually
using var aes = Aes.Create();
aes.Key = Encoding.UTF8.GetBytes("hardcoded-secret"); // 1) static key 2) wrong size 3) no MAC
✅ Correct: use IDataProtector
var protector = dp.CreateProtector("MyApp.MyFeature.v1");
var encrypted = protector.Protect(plaintext);
❌ Wrong: encrypting passwords
✅ Correct: hash, don't encrypt
❌ Wrong: forgetting SetApplicationName on multi-instance
builder.Services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo("/keys"));
// Each instance auto-generates a different app name → cookie doesn't roam
✅ Correct: explicit name
builder.Services.AddDataProtection()
.SetApplicationName("MyApp")
.PersistKeysToAzureBlobStorage(...);
Design patterns for this topic
Pattern 1 — "Shared key ring across instances"
- Intent: roaming cookies/tokens.
Pattern 2 — "Time-limited tokens via ITimeLimitedDataProtector"
- Intent: reset/confirmation tokens with built-in expiry.
Pattern 3 — "Purpose strings for isolation"
- Intent: prevent token reuse across features.
Pattern 4 — "Key Vault for key encryption"
- Intent: HSM-protected key ring.
Pattern 5 — "Versioned purpose strings"
- Intent: smooth migration when changing token format.
Pros & cons / trade-offs
| Aspect | Pros | Cons |
|---|---|---|
| Auto-rotation | No manual key mgmt | Lose ring = lose data |
| Built-in MAC | Tampering detected | Symmetric only |
| Purpose strings | Compartmentalization | Strings are conventions, not enforced |
| Storage providers | Pluggable | Must configure for production |
When to use / when to avoid
- Use for cookies, tokens, short-lived sensitive data.
- Avoid for passwords, long-term archives, cross-service comms.
- Avoid rolling your own AES — DPAPI handles MAC, IV, key rotation.
Interview Q&A
Q1. What does DPAPI protect? Cookies, anti-forgery tokens, OAuth state, your custom encrypted data — short-lived secrets at rest.
Q2. What goes wrong on multi-instance without shared keys? Each instance generates its own keys. Cookies from one don't decrypt on others. Users randomly logged out.
Q3. Why purpose strings? Cryptographic isolation — a token protected for purpose A won't unprotect under purpose B. Prevents cross-context reuse.
Q4. Why not use DPAPI for passwords? DPAPI is reversible encryption. Passwords need one-way hashing (PBKDF2, Argon2) so even with the keys, plaintext can't be recovered.
Q5. What's SetApplicationName? The key ring discriminator. Same name = same ring across instances. Different = isolated.
Q6. Time-limited data protector? Wraps IDataProtector to add expiration. Throws on read after expiry.
Q7. Default key lifetime? 90 days, with old keys retained for grace period. Configurable.
Q8. Where to store keys in Azure? Blob storage for the ring; Key Vault to encrypt the ring. HSM-backed if compliance requires.
Q9. Lose key ring — what happens? All data protected by it is unrecoverable. Cookies invalidated; reset tokens dead. Back up.
Q10. Senior pattern for token format changes? Version your purpose string (v1, v2). Old tokens still validate; new format isolates.
Gotchas / common mistakes
- ⚠️ No shared key store in multi-instance — random logouts.
- ⚠️ Encrypting passwords instead of hashing.
- ⚠️ No backup of key ring — irrecoverable data on loss.
- ⚠️ Same app name across environments — staging tokens valid in prod.
- ⚠️ Forgetting
Unprotectexception handling — tampered tokens crash request.