Store Data Protection keys in SQL Server (non-production)

Replaces the local filesystem path (which required IIS app pool write
access to inetpub\wwwroot\DataProtection-Keys) with SQL Server storage
via IDataProtectionKeyContext. Keys now survive deploys and IIS recycles
without any server-side folder permission setup.

Production continues to use Azure Blob Storage unchanged.

- Add Microsoft.AspNetCore.DataProtection.EntityFrameworkCore 8.0.11 to
  Web and Infrastructure projects
- ApplicationDbContext implements IDataProtectionKeyContext
- Migration AddDataProtectionKeys creates DataProtectionKeys table
- Program.cs: non-production path uses PersistKeysToDbContext

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-05 22:08:31 -04:00
parent c7a60a1fad
commit 713efbc2b6
7 changed files with 9635 additions and 20 deletions
@@ -21,6 +21,7 @@
<PackageReference Include="Fido2" Version="4.0.1" />
<PackageReference Include="Fido2.AspNet" Version="4.0.1" />
<PackageReference Include="Markdig" Version="0.40.0" />
<PackageReference Include="Microsoft.AspNetCore.DataProtection.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="8.0.11" />
<PackageReference Include="Microsoft.AspNetCore.Identity.UI" Version="8.0.11" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11">
+6 -16
View File
@@ -1,5 +1,6 @@
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.DataProtection.EntityFrameworkCore;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Identity;
@@ -147,22 +148,11 @@ if (builder.Environment.IsProduction())
}
else
{
// Use Azure Blob Storage when the connection string is available (dev/staging servers).
// Fall back to local filesystem for developer workstations where storage isn't configured.
var devStorageConnStr = builder.Configuration["Storage:ConnectionString"];
if (!string.IsNullOrEmpty(devStorageConnStr))
{
builder.Services.AddDataProtection()
.PersistKeysToAzureBlobStorage(devStorageConnStr, "dataprotection-dev", "keys.xml")
.SetApplicationName("PowderCoatingApp");
}
else
{
var keysPath = Path.Combine(builder.Environment.ContentRootPath, "DataProtection-Keys");
builder.Services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysPath))
.SetApplicationName("PowderCoatingApp");
}
// Non-production: store keys in SQL Server so they survive deploys and IIS app pool recycles
// without needing filesystem write permissions on the web root.
builder.Services.AddDataProtection()
.PersistKeysToDbContext<ApplicationDbContext>()
.SetApplicationName("PowderCoatingApp");
}
// Configure Identity