You deploy an ASP.NET Core app, or IIS recycles its app pool overnight, and the log fills up with this:
warn: Microsoft.AspNetCore.Session.SessionMiddleware[7]
Error unprotecting the session cookie.
System.Security.Cryptography.CryptographicException: The key {83f2e736-3392-4263-9f0f-ad943c51fe3e} was not found in the key ring.
At the same time, users report being logged out, and forms fail with "The antiforgery token could not be decrypted". The session warning is just the visible part. The cause is that the app has lost the keys it used to encrypt its cookies. Every cookie issued before that moment is now unreadable.
This happened on the API behind this site. It runs on IIS under Plesk, where the app pool has no loaded user profile, so ASP.NET Core kept its keys in memory and threw them away on every restart. Below is what I reproduced, the exact messages each cause produces, and the fix. The deploy side of IIS and Plesk 500 errors is in the Plesk 500 errors post.
The short fix: store the Data Protection keys in a folder that survives restarts and deploys, and give the app a fixed name:
services.AddDataProtection().PersistKeysToFileSystem(new DirectoryInfo(@"App_Data\keys")).SetApplicationName("MyApp"). Then make sure your deploy never deletes that folder. Users who already have old cookies will be logged out once more, and after that it stops.
What the error actually means
ASP.NET Core doesn't store session data, login tickets or antiforgery tokens in plain text. It encrypts them with the Data Protection system, which keeps a set of keys called the key ring. Each encrypted payload carries the ID of the key that made it. When a request comes in, the app looks up that ID in its ring. If it isn't there, you get "The key {...} was not found in the key ring".
Three components report it, in three different ways:
- Session middleware logs "Error unprotecting the session cookie" as a warning, then quietly starts a new, empty session.
- Cookie authentication logs "Cookies was not authenticated. Failure message:
Unprotect ticket failed", but only at Information level. With the default
"Microsoft.AspNetCore": "Warning"setting inappsettings.json, you never see it. The user is just signed out. - Antiforgery throws
AntiforgeryValidationException: "The antiforgery token could not be decrypted", which usually surfaces as a 400 on form posts.
That's why the session warning is the one people search for. It's the only one that shows up in a default log, even though the logouts hurt more.
What I tested
I built a minimal ASP.NET Core 9 app (runtime 9.0.8, Windows 11, Kestrel) with cookie sign-in, session and antiforgery. I signed in with curl, kept the cookie jar, restarted the app with a different key setup each time, and sent the old cookies back. These are the results.
1. Keys persisted, plain restart: nothing breaks
With keys stored in a folder and the app restarted on the same folder, the user was still signed
in and the old antiforgery token still validated. This is the control. One thing did reset: the
session value was empty after the restart. That isn't a key problem. AddDistributedMemoryCache()
keeps session data in process memory, so a restart loses it even when the cookie decrypts fine. If you
need sessions to survive restarts, use SQL Server or Redis for the session store as well.
2. Key folder wiped: "was not found in the key ring"
Pointing the restarted app at an empty folder is exactly what a deploy does when it deletes
the old site folder, including App_Data. The user came back anonymous, the antiforgery
post failed with "The antiforgery token could not be decrypted", and the log showed the error from the
top of this article, naming the old key's ID. The app also silently created a new key, logging:
No XML encryptor configured. Key {53f0f160-...} may be persisted to storage in unencrypted form.
3. Same keys, different application name: "The payload was invalid"
Data Protection isolates apps from one another using an application name. If you don't set one, it
is derived from the app's content root path. So moving the site to a different folder, or swapping
deployment slots, can change it. I kept the same key folder and changed only
SetApplicationName from SiteA to SiteB. The keys were found, but
nothing decrypted:
warn: Microsoft.AspNetCore.Session.SessionMiddleware[7]
Error unprotecting the session cookie.
System.Security.Cryptography.CryptographicException: The payload was invalid.
info: Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationHandler[7]
Cookies was not authenticated. Failure message: Unprotect ticket failed
The antiforgery error changed too, to "The required antiforgery cookie ".AspNetCore.Antiforgery.6asUPwDiMTw" is not present". The suffix of that cookie name is derived from the application name, so the app was looking for a cookie with a different name. If you see "payload was invalid" rather than "key not found", look at the application name before the keys.
4. Where the keys go by default
With no configuration, a normal Windows user account gets this at startup:
User profile is available. Using 'C:\Users\<you>\AppData\Local\ASP.NET\DataProtection-Keys' as key repository and Windows DPAPI to encrypt keys at rest.
That's why it works on your machine. An IIS app pool doesn't have that folder unless "Load User
Profile" is on, and on shared hosting such as Plesk you often can't control that setting. When there is no
profile and no registry hive set up for the pool, the framework uses an in-memory ring. I couldn't
reproduce that fallback outside IIS, but its warning is in the framework itself
(Microsoft.AspNetCore.DataProtection.dll, 9.0.8):
Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.
If that line is in your startup log, every app pool recycle, idle timeout and deploy logs your users out.
The fix
Three settings, all in Program.cs before builder.Build(). This is the
configuration this site's API uses now:
var keysDir = new DirectoryInfo(Path.GetFullPath("App_Data/keys", builder.Environment.ContentRootPath));
keysDir.Create();
var dataProtection = builder.Services
.AddDataProtection()
.PersistKeysToFileSystem(keysDir) // keys survive restarts and recycles
.SetApplicationName("MyApp"); // fixed, so moving the folder or slot doesn't change it
if (OperatingSystem.IsWindows())
{
dataProtection.ProtectKeysWithDpapi(protectToLocalMachine: true); // encrypt key files at rest
}
Notes on each line:
- The folder must be outside the release files. If your deploy copies the site over
App_Data, or deletes and re-extracts it, the keys go with it. KeepApp_Dataout of the delete step, the same way you keep uploads. - Use
ProtectKeysWithDpapi(protectToLocalMachine: true), not the per-user default. An app pool has no user profile to hold a per-user DPAPI key. Machine scope works without one. The trade-off is that the key files can only be decrypted on the same machine. - Set the application name once and never change it. Changing it logs every user out, the same as losing the keys.
After the change, the first deploy logs everyone out once. That's expected: the old cookies were encrypted with keys that no longer exist, and there's nothing to recover them with. From then on, restarts and recycles keep the same keys.
How to check it worked
- Sign in, then recycle the app pool or restart the app.
- Check that you are still signed in. If you are, the keys survived.
- Check the log. The "User profile is available" line should be gone, and no "Neither user profile nor HKLM registry available" line should appear. Either one would mean the keys went to memory.
- Check that
App_Data\keyscontainskey-*.xmlfiles, and that the timestamps don't change on every restart.





Join the conversation
No comments yet — be the first to share what you think.