ASP.NET Core Data Protection keys

Keep users signed in across restarts by storing the key ring in the private folder instead of DPAPI.

ASP.NET Core protects authentication cookies, antiforgery tokens and similar data with its Data Protection keys. On shared hosting the default key storage does not work, and the result is users who are signed out after every restart, antiforgery errors or even a login page that returns HTTP 500. The fix is a few lines in Program.cs.

How to recognise it

Typical entries in the logs of the website:

Using an in-memory repository. Keys will not be persisted to storage.
Neither user profile nor HKLM registry available. Using an ephemeral key repository.
No XML encryptor configured. Key {...} may be persisted to storage in unencrypted form.
System.Security.Cryptography.CryptographicException ... at ProtectWithDpapiCore
The antiforgery token could not be decrypted.

And the symptoms: everyone is logged out whenever the application restarts, forms fail with an antiforgery error after a deployment, or a page that uses IDataProtector throws an exception.

Why it happens

By default ASP.NET Core stores its keys in the Windows user profile of the account that runs the application and encrypts them with Windows DPAPI. The application pool of a hosted website runs without a Windows user profile, so neither the profile folder nor DPAPI is available. The application falls back to keys kept only in memory — new keys on every start.

Store the keys in the private folder

Every website has a private folder next to wwwroot. It is outside the public root, it is writable by your application, and deployments do not touch it. Keep the key ring there:

  1. Configure Data Protection

    In Program.cs add:

    var builder = WebApplication.CreateBuilder(args);
    
    var keysPath = Path.Combine(builder.Environment.ContentRootPath, "..", "private", "keys");
    
    builder.Services.AddDataProtection()
        .SetApplicationName("MyApplication")
        .PersistKeysToFileSystem(new DirectoryInfo(keysPath))
        .SetDefaultKeyLifetime(TimeSpan.FromDays(90));
    

    ContentRootPath is the wwwroot folder on the server, so ../private/keys resolves to the private folder of the website. Do not call ProtectKeysWithDpapi() or ProtectKeysWithDpapiNG().

  2. Publish and restart

    Publish the application. On the first start it creates private/keys/key-*.xml. Over FTP the structure looks like this:

    /wwwroot      your application
    /private
      /keys       key-*.xml
    
  3. Clear old cookies

    Cookies issued while the application was still using in-memory keys cannot be decrypted any more. Sign in again in a fresh browser session; the antiforgery errors disappear with the old cookies.

Note

The warning "No XML encryptor configured" remains and is expected: without DPAPI the keys are stored as plain XML. The private folder is accessible only to your own website — no other customer or process can read it — so this is not a security problem. Encrypting the keys adds complexity and brings no benefit here.

Certificates and PFX files

The same applies to certificates. The Windows certificate store is not available to your application, so do not look certificates up by thumbprint. Put the PFX file into private/certs and load it from the file:

var certPath = Path.Combine(builder.Environment.ContentRootPath, "..", "private", "certs", "signing.pfx");
var certificate = new X509Certificate2(certPath, "pfx-password",
    X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.EphemeralKeySet);

This works for IdentityServer, OpenIddict, Duende, JWT signing keys and for protecting the Data Protection keys with a certificate (ProtectKeysWithCertificate), if you need that.

Load user profile

Premium websites can switch Load user profile on: Websites → your website → Scripting → Advanced settings. The application pool then gets a Windows profile and DPAPI works. It is intended for third-party libraries that insist on a user profile; it makes the application start slower and use more resources, so the recommended setting is Disabled. Store the keys in private/keys instead.

On the Free plan the option is not available.

Still stuck? Our support team is happy to help.
Ask the community Open a support ticket