ASP.Net Core Cookie Authentication is not persistant
Asked Answered
S

1

7

I started developing websites using ASP.Net Core 2.2. I'm implementing login/logout by a custom cookie authentication (not Identity).

Please see or clone the repo:

git clone https://github.com/mrmowji/aspcore-custom-cookie-authentication.git .

... or read the following code snippets.

Here is the code in Startup.cs:

public void ConfigureServices(IServiceCollection services) {
  ...

  services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
    .AddCookie(options => {
      options.LoginPath = new PathString("/login");
      options.ExpireTimeSpan = TimeSpan.FromDays(30);
      options.Cookie.Expiration = TimeSpan.FromDays(30);
      options.SlidingExpiration = true;
    });

  ...

public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
  ...

  app.UseHttpsRedirection();
  app.UseStaticFiles();
  app.UseCookiePolicy();

  app.UseAuthentication();

  app.UseMvc(routes =>
  {
    ...

Here is the code of Login action:

public async Task<IActionResult> Login(LoginViewModel userToLogin) {
  var username = "username"; // just to test
  var password = "password"; // just to test
  if (userToLogin.UserName == username && userToLogin.Password == password) {
    var claims = new List<Claim> {
      new Claim(ClaimTypes.Name, "admin"),
      new Claim(ClaimTypes.Role, "Administrator"),
    };

  var claimsIdentity = new ClaimsIdentity(
    claims, CookieAuthenticationDefaults.AuthenticationScheme);

  var authProperties = new AuthenticationProperties {
    AllowRefresh = true,
    ExpiresUtc = DateTimeOffset.UtcNow.AddDays(10),
    IsPersistent = true,
  };

  await HttpContext.SignInAsync(
    CookieAuthenticationDefaults.AuthenticationScheme,
    new ClaimsPrincipal(claimsIdentity),
    authProperties);

    ...

Cookies are set as expected. I have a .AspNetCore.Cookies cookie with expiration date of 10 days later. But after about 30 minutes, the user is logged out. How to force the authenticated user to stay logged in, even after the browser is closed?

Sopher answered 7/6, 2019 at 8:18 Comment(10)
@LeonardoSeccia I didn't check that. Does he/she have to?Sopher
You might have an IdleTimeout set to 30 minutes... Either way. can you share your ValidatePrincipal method too please (if you have overriden that)?Eyot
@LeonardoSeccia I don't have one. I haven't seen anything about ValidatePrincipal in forums which suggest solutions or tut this type of authentication. Should I implement it?Sopher
Don't need to but it is often used to sync auth with backend... I thought you might have an issue in there... If you haven't overriden it then you are fine ;-)Eyot
What version of core are you running? 2.1?Eyot
@LeonardoSeccia 2.2Sopher
another thought... Is your server storing cookie decryption keys permanently? If not, whenever the server or the app pool restarts, users will have to login again...Eyot
Can you boil this down to a minimal reproducible example?Hexachord
@LeonardoSeccia I'll check that and post the results. Thanks.Sopher
@KirkLarkin Sorry. My bad. I'll try to create a repo if Leonardo's suggestion doesn't work.Thanks for the link.Sopher
S
10

Thanks to @LeonardoSeccia I could find the answer. Please read the comments on the main question.

I just needed to add data protection service inside ConfigureServices method and provide a way to store/persist the keys (which are used to encrypt/decrypt sensitive data) somewhere, otherwise whenever the server or the app pool restarts, new keys would be generated and old encrypted data (including authentication cookies) will not get decrypted the way they must, which results in a failed authentication. It's necessary if you're deploying to a shared host like me.

If you've used ASP.Net from .Net Framework before, this Data Protection concept is somehow equivalent to MachineKey.

I've decided to use a file to store the keys. Here is the final changes in Startup.cs:

public Startup(IConfiguration configuration, IHostingEnvironment environment) {
  Configuration = configuration;
  hostingEnvironment = environment;
}

private IHostingEnvironment hostingEnvironment;

public IConfiguration Configuration { get; }

// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
  // create a directory for keys if it doesn't exist
  // it'll be created in the root, beside the wwwroot directory
  var keysDirectoryName = "Keys";
  var keysDirectoryPath = Path.Combine(hostingEnvironment.ContentRootPath, keysDirectoryName);
  if (!Directory.Exists(keysDirectoryPath)) {
    Directory.CreateDirectory(keysDirectoryPath);
  }
  services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(keysDirectoryPath))
    .SetApplicationName("CustomCookieAuthentication");

  services.Configure<CookiePolicyOptions>(options =>
  {
  ...
Sopher answered 7/6, 2019 at 11:57 Comment(5)
I just encountered the same issue, it helps, thanks.Necrology
The repo is not availableMcnelly
@Mcnelly I must have removed it by mistake. Sorry.Sopher
No problem, It worked perfectly :) thank youMcnelly
A drawback with using a custom folder location for storing the keys is that they will not be encrypted at rest per default. Instead one can use the solution to your problem described here (https://mcmap.net/q/605351/-neither-user-profile-nor-hklm-registry-available-using-an-ephemeral-key-repository-protected-data-will-be-unavailable-when-application-exits).Fuji

© 2022 - 2024 — McMap. All rights reserved.