Search code examples
asp.net-coreiis.net-7.0

.NET 7 cookie authentication lost on restart / recycle


I have a .NET 7 web app in IIS that uses cookie authentication. Currently, any app recycle at all kicks the user off. This includes a website stop/start and code rebuild and deploy obviously.

I've gone through all the many questions for this scenario and I think I've got everything set up as it should be, but it is still doing it.

Things I have done

  1. Changed setProfileEnvironmentfrom false to true in applicationHost.config (and restarted IIS afterwards)

  2. Explicity set cookie sliding expiration:

            options.SlidingExpiration = true;
            options.ExpireTimeSpan = TimeSpan.FromMinutes(120);
    
  3. Set persistence and absolute expiration:

            await HttpContextAccessor.HttpContext.SignInAsync(
                CookieAuthenticationDefaults.AuthenticationScheme,
                principal,
                new AuthenticationProperties
                {
                    IsPersistent = true,
                    AllowRefresh = true,
                    ExpiresUtc = DateTime.UtcNow.AddDays(7 * 4)
                }
            );
    
  4. Added data protection before the builder.Build() call (although I'm not even sure this should be required if the user profile environment is set - the docs imply that .NET will manage it itself then?)

        var keysfolder = "_keys";
    
        var keyspath = Path.Combine(environment.ContentRootPath, keysfolder);
    
        if (!Directory.Exists(keyspath))
            Directory.CreateDirectory(keyspath);
    
        services.AddDataProtection()
              .PersistKeysToFileSystem(new DirectoryInfo(keyspath))
              .SetDefaultKeyLifetime(TimeSpan.FromDays(7 * 4));
    

I have tried adding the data protection both before the AddCookie call and after it; no effect.

The user is still logged out on every recycle and every restart.

What am I missing?

EDIT:

In case this helps future me or anyone else, this was a real XY problem, because none of the above is relevant.

As the docs imply, for a single server cookie authenticated site you do not need data protection to persist cookies between restarts.

The problem was actually in some later code of my own that stored a static reference to the user; this was then used deep in some permission checking, but being lost and not repopulated correctly on recycle.


Solution

  • DESIRED BEHAVIOUR

    Your requirement is a good one. Application recycling should not force users to re-authenticate, which would be considered a major problem in some use cases. Cookies should help to enable that. This may not be a complete answer but my answer may give you a few clues.

    DOT NET COOKIES

    The Microsoft cookie issuing code is meant to be stateless by default, if no session store is configured. Therefore, when a cookie is received, the user ID and expiry time should be determined solely from the cookie in HTTP requests.

    IIS BEHAVIOUR

    I wonder if IIS is overriding the default .NET behaviour. I seem to remember an IIS setting something like system.web/sessionState that you could set to Use Cookies in the web configuration?

    COOKIE ENCRYPTION KEYS

    Cookies are encrypted using a symmetric encryption key. You usually need to override Microsoft's default implementation, eg in clustered setups. Yet you seem to be doing this in a resilient way, so I doubt that is the issue. Still, I would focus very closely on capturing the HTTP request and response from a user browser after an iisreset. Is it definitely a cookie being rejected?

    SIMILAR PROJECT

    At Curity, where I work, we produced a related project a while back. It was a utility API to issue cookies for OAuth secured Single Page Apps in cloud native deployments.

    The reason I mention it is that we wanted the same stateless and resilient cookie behaviour that you seem to want. Our requirements were met quite easily by the Kestrel web server.

    So if you find nothing else you might do a spike that runs your web app in Kestrel. That would enable you to determine whether .NET or IIS is the source of your problems.