$npx -y skills add wshaddix/dotnet-skills --skill data-protectionASP.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 val
| 1 | ## Rationale |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | ## Patterns |
| 6 | |
| 7 | ### Pattern 1: Data Protection Configuration |
| 8 | |
| 9 | Configure Data Protection with proper key storage and application isolation. |
| 10 | |
| 11 | ```csharp |
| 12 | // Program.cs - Basic configuration |
| 13 | builder.Services.AddDataProtection() |
| 14 | .SetApplicationName("MyApp") // Critical for multi-app environments |
| 15 | .PersistKeysToFileSystem(new DirectoryInfo(@"\shared\keys")) |
| 16 | .ProtectKeysWithDpapi(); // Windows only |
| 17 | |
| 18 | // Cross-platform key protection |
| 19 | builder.Services.AddDataProtection() |
| 20 | .SetApplicationName("MyApp") |
| 21 | .PersistKeysToFileSystem(new DirectoryInfo(@"/shared/keys")) |
| 22 | .ProtectKeysWithCertificate( |
| 23 | new X509Certificate2("/certs/dataprotection.pfx", "password")); |
| 24 | |
| 25 | // Azure Blob Storage for key persistence (production) |
| 26 | builder.Services.AddDataProtection() |
| 27 | .SetApplicationName("MyApp") |
| 28 | .PersistKeysToAzureBlobStorage(blobUri) |
| 29 | .ProtectKeysWithAzureKeyVaultKey(keyVaultKeyId); |
| 30 | |
| 31 | // Redis for key storage in containerized environments |
| 32 | builder.Services.AddDataProtection() |
| 33 | .SetApplicationName("MyApp") |
| 34 | .PersistKeysToStackExchangeRedis(connection, "DataProtection-Keys") |
| 35 | .ProtectKeysWithCertificate(certificate); |
| 36 | ``` |
| 37 | |
| 38 | ### Pattern 2: Protecting Sensitive Data at Rest |
| 39 | |
| 40 | Use Data Protection to encrypt sensitive data before storing in databases. |
| 41 | |
| 42 | ```csharp |
| 43 | public interface IDataProtectorService |
| 44 | { |
| 45 | string Protect(string plainText); |
| 46 | string? Unprotect(string protectedText); |
| 47 | byte[] Protect(byte[] plainData); |
| 48 | byte[]? Unprotect(byte[] protectedData); |
| 49 | } |
| 50 | |
| 51 | public class DataProtectorService : IDataProtectorService |
| 52 | { |
| 53 | private readonly IDataProtector _protector; |
| 54 | private readonly ILogger<DataProtectorService> _logger; |
| 55 | |
| 56 | public DataProtectorService( |
| 57 | IDataProtectionProvider dataProtectionProvider, |
| 58 | ILogger<DataProtectorService> logger) |
| 59 | { |
| 60 | // Create purpose-specific protector |
| 61 | _protector = dataProtectionProvider.CreateProtector("MyApp.SensitiveData.v1"); |
| 62 | _logger = logger; |
| 63 | } |
| 64 | |
| 65 | public string Protect(string plainText) |
| 66 | { |
| 67 | if (string.IsNullOrEmpty(plainText)) |
| 68 | return plainText; |
| 69 | |
| 70 | try |
| 71 | { |
| 72 | return _protector.Protect(plainText); |
| 73 | } |
| 74 | catch (CryptographicException ex) |
| 75 | { |
| 76 | _logger.LogError(ex, "Failed to protect data"); |
| 77 | throw; |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | public string? Unprotect(string protectedText) |
| 82 | { |
| 83 | if (string.IsNullOrEmpty(protectedText)) |
| 84 | return protectedText; |
| 85 | |
| 86 | try |
| 87 | { |
| 88 | return _protector.Unprotect(protectedText); |
| 89 | } |
| 90 | catch (CryptographicException ex) |
| 91 | { |
| 92 | _logger.LogWarning(ex, "Failed to unprotect data - may be corrupted or from different key"); |
| 93 | return null; |
| 94 | } |
| 95 | } |
| 96 | |
| 97 | public byte[] Protect(byte[] plainData) |
| 98 | { |
| 99 | ArgumentNullException.ThrowIfNull(plainData); |
| 100 | return _protector.Protect(plainData); |
| 101 | } |
| 102 | |
| 103 | public byte[]? Unprotect(byte[] protectedData) |
| 104 | { |
| 105 | ArgumentNullException.ThrowIfNull(protectedData); |
| 106 | |
| 107 | try |
| 108 | { |
| 109 | return _protector.Unprotect(protectedData); |
| 110 | } |
| 111 | catch (CryptographicException ex) |
| 112 | { |
| 113 | _logger.LogWarning(ex, "Failed to unprotect binary data"); |
| 114 | return null; |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | // Entity with encrypted fields |
| 120 | public class PaymentMethod |
| 121 | { |
| 122 | public Guid Id { get; set; } |
| 123 | public required string UserId { get; set; } |
| 124 | |
| 125 | // Store encrypted card number |
| 126 | public required string EncryptedCardNumber { get; set; } |
| 127 | |
| 128 | // Last 4 digits stored in clear for display |
| 129 | public required string CardLastFourDigits { get; set; } |
| 130 | |
| 131 | public required string EncryptedExpirationDate { get; set; } |
| 132 | public required string CardType { get; set; } |
| 133 | public DateTimeOffset CreatedAt { get; set; } |
| 134 | |
| 135 | [NotMapped] |
| 136 | public string? CardNumber { get; set; } |
| 137 | |
| 138 | [NotMapped] |
| 139 | public string? ExpirationDate { get; set; } |
| 140 | } |
| 141 | |
| 142 | // Repository with encryption/decryption |
| 143 | public class PaymentMethodRepository |
| 144 | { |
| 145 | private readonly ApplicationDbContext _dbContext; |
| 146 | private readonly IDataProtectorService _protector; |
| 147 | |
| 148 | public PaymentMethodRepository( |
| 149 | ApplicationDbContext dbContext, |
| 150 | IDataProtectorService protector) |