Claude
Skills
Sign in
Back

data-protection

Included with Lifetime
$97 forever

ASP.NET Core Data Protection API patterns for encryption, key management, and secure data handling in web applications. Use when protecting sensitive data at rest or in transit, managing encryption keys in ASP.NET Core applications, or implementing secure token generation and validation.

Backend & APIs

What this skill does


## Rationale

Protecting sensitive data is critical for security compliance and user privacy. The ASP.NET Core Data Protection API provides a secure, easy-to-use framework for encryption, key management, and data protection. Without proper patterns, applications risk data exposure, key management failures, and compliance violations. These patterns ensure secure, maintainable data protection practices.

## Patterns

### Pattern 1: Data Protection Configuration

Configure Data Protection with proper key storage and application isolation.

```csharp
// Program.cs - Basic configuration
builder.Services.AddDataProtection()
    .SetApplicationName("MyApp") // Critical for multi-app environments
    .PersistKeysToFileSystem(new DirectoryInfo(@"\shared\keys"))
    .ProtectKeysWithDpapi(); // Windows only

// Cross-platform key protection
builder.Services.AddDataProtection()
    .SetApplicationName("MyApp")
    .PersistKeysToFileSystem(new DirectoryInfo(@"/shared/keys"))
    .ProtectKeysWithCertificate(
        new X509Certificate2("/certs/dataprotection.pfx", "password"));

// Azure Blob Storage for key persistence (production)
builder.Services.AddDataProtection()
    .SetApplicationName("MyApp")
    .PersistKeysToAzureBlobStorage(blobUri)
    .ProtectKeysWithAzureKeyVaultKey(keyVaultKeyId);

// Redis for key storage in containerized environments
builder.Services.AddDataProtection()
    .SetApplicationName("MyApp")
    .PersistKeysToStackExchangeRedis(connection, "DataProtection-Keys")
    .ProtectKeysWithCertificate(certificate);
```

### Pattern 2: Protecting Sensitive Data at Rest

Use Data Protection to encrypt sensitive data before storing in databases.

```csharp
public interface IDataProtectorService
{
    string Protect(string plainText);
    string? Unprotect(string protectedText);
    byte[] Protect(byte[] plainData);
    byte[]? Unprotect(byte[] protectedData);
}

public class DataProtectorService : IDataProtectorService
{
    private readonly IDataProtector _protector;
    private readonly ILogger<DataProtectorService> _logger;

    public DataProtectorService(
        IDataProtectionProvider dataProtectionProvider,
        ILogger<DataProtectorService> logger)
    {
        // Create purpose-specific protector
        _protector = dataProtectionProvider.CreateProtector("MyApp.SensitiveData.v1");
        _logger = logger;
    }

    public string Protect(string plainText)
    {
        if (string.IsNullOrEmpty(plainText))
            return plainText;

        try
        {
            return _protector.Protect(plainText);
        }
        catch (CryptographicException ex)
        {
            _logger.LogError(ex, "Failed to protect data");
            throw;
        }
    }

    public string? Unprotect(string protectedText)
    {
        if (string.IsNullOrEmpty(protectedText))
            return protectedText;

        try
        {
            return _protector.Unprotect(protectedText);
        }
        catch (CryptographicException ex)
        {
            _logger.LogWarning(ex, "Failed to unprotect data - may be corrupted or from different key");
            return null;
        }
    }

    public byte[] Protect(byte[] plainData)
    {
        ArgumentNullException.ThrowIfNull(plainData);
        return _protector.Protect(plainData);
    }

    public byte[]? Unprotect(byte[] protectedData)
    {
        ArgumentNullException.ThrowIfNull(protectedData);
        
        try
        {
            return _protector.Unprotect(protectedData);
        }
        catch (CryptographicException ex)
        {
            _logger.LogWarning(ex, "Failed to unprotect binary data");
            return null;
        }
    }
}

// Entity with encrypted fields
public class PaymentMethod
{
    public Guid Id { get; set; }
    public required string UserId { get; set; }
    
    // Store encrypted card number
    public required string EncryptedCardNumber { get; set; }
    
    // Last 4 digits stored in clear for display
    public required string CardLastFourDigits { get; set; }
    
    public required string EncryptedExpirationDate { get; set; }
    public required string CardType { get; set; }
    public DateTimeOffset CreatedAt { get; set; }
    
    [NotMapped]
    public string? CardNumber { get; set; }
    
    [NotMapped]
    public string? ExpirationDate { get; set; }
}

// Repository with encryption/decryption
public class PaymentMethodRepository
{
    private readonly ApplicationDbContext _dbContext;
    private readonly IDataProtectorService _protector;

    public PaymentMethodRepository(
        ApplicationDbContext dbContext,
        IDataProtectorService protector)
    {
        _dbContext = dbContext;
        _protector = protector;
    }

    public async Task<PaymentMethod> AddAsync(PaymentMethod paymentMethod)
    {
        // Encrypt sensitive fields before saving
        paymentMethod.EncryptedCardNumber = _protector.Protect(paymentMethod.CardNumber!);
        paymentMethod.EncryptedExpirationDate = _protector.Protect(paymentMethod.ExpirationDate!);
        
        // Store last 4 digits for display
        paymentMethod.CardLastFourDigits = paymentMethod.CardNumber![^4..];
        
        // Clear plain text fields (they're NotMapped anyway)
        paymentMethod.CardNumber = null;
        paymentMethod.ExpirationDate = null;
        
        _dbContext.PaymentMethods.Add(paymentMethod);
        await _dbContext.SaveChangesAsync();
        
        return paymentMethod;
    }

    public async Task<PaymentMethod?> GetByIdAsync(Guid id)
    {
        var paymentMethod = await _dbContext.PaymentMethods
            .FirstOrDefaultAsync(p => p.Id == id);

        if (paymentMethod != null)
        {
            // Decrypt for use
            paymentMethod.CardNumber = _protector.Unprotect(paymentMethod.EncryptedCardNumber);
            paymentMethod.ExpirationDate = _protector.Unprotect(paymentMethod.EncryptedExpirationDate);
        }

        return paymentMethod;
    }

    public async Task<List<PaymentMethod>> GetByUserIdAsync(string userId)
    {
        // Return without decrypted data for listing
        return await _dbContext.PaymentMethods
            .AsNoTracking()
            .Where(p => p.UserId == userId)
            .Select(p => new PaymentMethod
            {
                Id = p.Id,
                UserId = p.UserId,
                CardLastFourDigits = p.CardLastFourDigits,
                CardType = p.CardType,
                CreatedAt = p.CreatedAt
                // Don't include encrypted fields or decrypted data
            })
            .ToListAsync();
    }
}
```

### Pattern 3: Time-Limited Protection

Create tokens that expire after a set time using time-limited data protectors.

```csharp
public class TokenService
{
    private readonly ITimeLimitedDataProtector _protector;
    private readonly ILogger<TokenService> _logger;

    public TokenService(IDataProtectionProvider dataProtectionProvider, ILogger<TokenService> logger)
    {
        var baseProtector = dataProtectionProvider.CreateProtector("MyApp.TimeLimitedTokens");
        _protector = baseProtector.ToTimeLimitedDataProtector();
        _logger = logger;
    }

    public string GenerateToken(string purpose, string userId, TimeSpan lifetime)
    {
        var payload = JsonSerializer.Serialize(new TokenPayload
        {
            Purpose = purpose,
            UserId = userId,
            IssuedAt = DateTimeOffset.UtcNow
        });

        return _protector.Protect(payload, lifetime);
    }

    public TokenPayload? ValidateToken(string token, string expectedPurpose)
    {
        try
        {
            var payload = _protector.Unprotect(token, out var expiration);
            var data = JsonSerializer.Deserialize<TokenPayload>(payload);

            if (data?.Purpose != expectedPurpose)
            {
                _logger.LogWarning("Token purpose mismatch: expected {Expected}, got {Actual}",
                    expectedPurpo

Related in Backend & APIs