The ASP.NET Core Data Protection API is a cryptography subsystem for protecting sensitive data — encrypting payloads that must be stored or transmitted and decrypted later. It is used internally by ASP.NET Core for anti-forgery tokens, cookie encryption, and temp data. You can use it directly to protect any sensitive string or byte array: password reset tokens, email confirmation tokens, two-factor codes, or any data that needs to be encrypted at rest or in transit. In a multi-instance deployment (multiple web servers), key storage must be shared — each server must be able to decrypt data protected by any other server.
Using IDataProtector
// ── Registration — done automatically by ASP.NET Core ─────────────────────
// AddDataProtection is called by WebApplication.CreateBuilder automatically.
// You only need explicit configuration for key storage and key lifetime.
// ── Inject and use IDataProtectionProvider ────────────────────────────────
public class TokenService(
IDataProtectionProvider protectionProvider,
ILogger<TokenService> logger)
{
// Purpose string isolates this protector from others — tokens from one
// purpose cannot be decrypted by a protector with a different purpose
private readonly IDataProtector _protector =
protectionProvider.CreateProtector("BlogApp.TokenService.EmailConfirmation");
public string CreateEmailConfirmationToken(string userId, string email)
{
var payload = $"{userId}:{email}:{DateTime.UtcNow:O}";
string token = _protector.Protect(payload);
logger.LogDebug("Created email confirmation token for {UserId}", userId);
return token; // URL-safe base64 encoded encrypted string
}
public (string UserId, string Email, DateTime CreatedAt) ValidateEmailToken(string token)
{
try
{
string payload = _protector.Unprotect(token);
var parts = payload.Split(':');
return (parts[0], parts[1], DateTime.Parse(parts[2]));
}
catch (CryptographicException ex)
{
// Token is invalid, tampered, expired, or from a different key
logger.LogWarning(ex, "Invalid email confirmation token.");
throw new SecurityTokenException("Invalid or expired token.");
}
}
}
// ── Time-limited protector — token expires after 24 hours ─────────────────
private readonly ITimeLimitedDataProtector _timeLimitedProtector =
protectionProvider
.CreateProtector("BlogApp.PasswordReset")
.ToTimeLimitedDataProtector();
public string CreatePasswordResetToken(string userId)
=> _timeLimitedProtector.Protect(userId, lifetime: TimeSpan.FromHours(24));
public string ValidatePasswordResetToken(string token)
=> _timeLimitedProtector.Unprotect(token); // throws if expired
Key Storage Configuration
// ── File system key storage (single server or shared network path) ─────────
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo("/var/keys"))
.SetApplicationName("BlogApp") // must be same across all instances
.SetDefaultKeyLifetime(TimeSpan.FromDays(90));
// ── Azure Blob Storage + Key Vault (multi-server, production) ─────────────
// dotnet add package Microsoft.AspNetCore.DataProtection.AzureStorage
// dotnet add package Microsoft.AspNetCore.DataProtection.AzureKeyVault
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(
new Uri("https://blogappstorage.blob.core.windows.net/keys/keys.xml"),
new DefaultAzureCredential())
.ProtectKeysWithAzureKeyVault(
new Uri("https://blogappvault.vault.azure.net/keys/data-protection"),
new DefaultAzureCredential())
.SetApplicationName("BlogApp");
// ── Redis key storage (for Redis-based session/cache setups) ──────────────
// builder.Services.AddDataProtection()
// .PersistKeysToStackExchangeRedis(redis, "DataProtection-Keys")
// .SetApplicationName("BlogApp");
Common Mistakes
Mistake 1 — Not configuring shared key storage in multi-server deployments
❌ Wrong — each server has its own key ring; session cookies, anti-forgery tokens fail across servers.
✅ Correct — configure Azure Blob, Redis, or database key storage shared across all instances.
Mistake 2 — Using the same purpose string for different token types
❌ Wrong — password reset tokens can be decoded as email confirmation tokens:
✅ Correct — always use specific purpose strings: “BlogApp.EmailConfirmation”, “BlogApp.PasswordReset”.