After a quiet app pool recycle, half the site looks “randomly” broken: auth cookies no longer unprotect, antiforgery tokens fail validation, and anything else that touched Data Protection starts 400ing. We see this on Windows hosts whenever a .NET 10 app never pinned its key ring and IIS recycled the worker for idle timeout, a deploy, or a private-bytes limit.
The app did not lose its database session store. It lost the keys that encrypt the payload. Default Data Protection is fine on a developer laptop. On IIS it is ephemeral unless you say otherwise.
#Why the ring disappears on IIS
If you never call PersistKeysToFileSystem (or an equivalent), keys live with the process. In-process ANCM and out-of-process Kestrel both die with the w3wp/dotnet lifetime, so a recycle mints a new ring. Keys stored under the app pool’s user profile also fail when Load User Profile is false—the common shared-hosting default—so the runtime falls back to an in-memory ring you will not see in any folder.
Publishing over the site can make it worse: a key directory inside the content root gets deleted by Web Deploy or a folder-swap cutover, which is the same outage with extra steps.
#Pin keys outside the publish tree
Create a directory the app pool identity can read and write, outside wwwroot and outside the folder you replace on deploy. Point Data Protection at it at startup, and keep the app name stable so purpose strings match across instances.
builder.Services.AddDataProtection()
.SetApplicationName("Contoso.Store")
.PersistKeysToFileSystem(
new DirectoryInfo(@"C:\inetpub\dp-keys\contoso-store"));
ACL that folder to the app pool identity only (no Users, no IIS_IUSRS blanket write). On a multi-site box use one directory per app so rings never mix. If you run more than one worker for the same site, every worker must see the same path—local disk is enough on a single machine; do not invent a network share unless you already operate one cleanly.
Opinionated don’t: do not PersistKeysToFileSystem under the site’s publish directory, App_Data that gets wiped, or a temp path. You will ship a clean deploy and immediately invalidate every protected cookie. Persist once, recycle on purpose, and confirm an old auth cookie still unprotects before you call the cutover done.
Comments
No comments yet