Skip to content

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 IDataProtectionProvider to 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

builder.Services.AddDataProtection()
    .SetDefaultKeyLifetime(TimeSpan.FromDays(30));

Shorter = more frequent rotation = smaller blast radius if a key leaks. But you must persist enough history.

Cross-environment isolation

.SetApplicationName(env.IsProduction() ? "MyApp.Prod" : "MyApp.Dev")

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

user.PasswordHash = protector.Protect(plaintextPassword);   // reversible — wrong tool

✅ Correct: hash, don't encrypt

user.PasswordHash = passwordHasher.HashPassword(user, plaintextPassword);   // PBKDF2/Argon2

❌ 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 Unprotect exception handling — tampered tokens crash request.

Further reading