locked
Http cookie not deleted when user closes window RRS feed

  • Question

  • User-462241089 posted

    So I implemented cookie authentication in my app to log a user in with their AD credentials, but I noticed that the user is still logged in even after they close the browser window. It seems like the cookie is persistent between sessions.

    What I want to do is set the cookie to die if the browser window is closed, but also set the ExpiresUtc to be 1 hour (so that the user has enough time to browse my site). I searched online for additional help, but most of the help suggest setting the ExpiresUtc to -1, which seems (to me) to not be the best option to solve this problem.

    So basically, they are logged out if they close their browser.

    Here is my code:

                    var claims = new List<Claim>
                    {
                        new Claim(ClaimTypes.Name, user.username),
                        new Claim("username", user.username),
                        new Claim(ClaimTypes.Role, "User"),
                    };
    
                    var claimsIdentity = new ClaimsIdentity(
                        claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
                    var authProperties = new AuthenticationProperties
                    {
                        AllowRefresh = false,
    
                        ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
    
                        IsPersistent = true
                    };
    
                    try
                    {
                        await HttpContext.SignInAsync(
                            CookieAuthenticationDefaults.AuthenticationScheme,
                            new ClaimsPrincipal(claimsIdentity),
                            authProperties);
                    }
                    catch (Exception)
                    {
                        //Return the page with an error stating that
                        throw;
                    }

    Any Ideas, guys? Thanks!

    Friday, July 31, 2020 5:02 PM

Answers

  • User-462241089 posted

    So after talking with you guys, trying myself for several hours, reading many articles, and even talking with the authors of the docs, it appears that is is impossible to code this behavior.

    The reason why is because javascript cannot "detect" a close tab or window event. If there was such a way, then I could code this behavior, but there isn't. I guess I will just have to love with this. The user's browser is the one who decides if the session is closed out or not, and this is just how browsers and http sessions work. Can't change it.

    This is an unforeseen caveat with cookie authentication that wasn't mentioned to me upfront, and I sort of wish it was. Hopefully, the doc authors will take my feedback to heart and will edit the docs to reflect this.

    Good luck to whoever stumbles upon this thread!

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, August 4, 2020 1:18 PM

All replies

  • User475983607 posted

    So I implemented cookie authentication in my app to log a user in with their AD credentials, but I noticed that the user is still logged in even after they close the browser window. It seems like the cookie is persistent between sessions.

    You configured the cookie to persist when the browser is closed. 

                    var claims = new List<Claim>
                    {
                        new Claim(ClaimTypes.Name, user.username),
                        new Claim("username", user.username),
                        new Claim(ClaimTypes.Role, "User"),
                    };
    
                    var claimsIdentity = new ClaimsIdentity(
                        claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
                    var authProperties = new AuthenticationProperties
                    {
                        AllowRefresh = false,
    
                        ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
    
                        IsPersistent = true
                    };
    
                    try
                    {
                        await HttpContext.SignInAsync(
                            CookieAuthenticationDefaults.AuthenticationScheme,
                            new ClaimsPrincipal(claimsIdentity),
                            authProperties);
                    }
                    catch (Exception)
                    {
                        //Return the page with an error stating that
                        throw;
                    }

    Friday, July 31, 2020 5:15 PM
  • User-462241089 posted

    I just set it to false, and it is still present. 

    Friday, July 31, 2020 6:50 PM
  • User475983607 posted

    MarcusAtMars

    I just set it to false, and it is still present. 

    Log out then log in.  The original cookie was created to persist.  The log out should clear the cookie.  Please use the browser's dev tools as it shows cookie information. 

    Friday, July 31, 2020 6:59 PM
  • User-462241089 posted

    I cleared all my cookies, and I am logged out at first, but if I log in to create a new cookie and then close the window, and then open a new one with iis express, I am still logged in.

    My code is, as follows:

                    var authProperties = new AuthenticationProperties
                    {
                        AllowRefresh = false,
    
                        ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
    
                        IsPersistent = false
                    };

    Friday, July 31, 2020 8:17 PM
  • User475983607 posted

    I cleared all my cookies, and I am logged out at first, but if I log in to create a new cookie and then close the window, and then open a new one with iis express, I am still logged in.

    My code is, as follows:

                    var authProperties = new AuthenticationProperties
                    {
                        AllowRefresh = false,
    
                        ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(60),
    
                        IsPersistent = false
                    };

    I cannot reproduce this behavior.  There must be somehting else going on with your code or process that forum members cannot see.

    Below is my test code.  The code is pretty much a copy and paste from the Cookie auth docs; https://docs.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-3.1

    Configure Services

     services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
                    .AddCookie(options => {
                        options.LoginPath = "/Account/Index";
                        });

    Configure

            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                    app.UseHsts();
                }
                app.UseHttpsRedirection();
                app.UseStaticFiles();
    
                app.UseRouting();
    
                app.UseAuthentication();
                app.UseAuthorization();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapControllerRoute(
                        name: "default",
                        pattern: "{controller=Home}/{action=Index}/{id?}");
                });
            }

    Account controller for login

    public class Account : Controller
        {
            public IActionResult Index()
            {
                return View();
            }
    
            [HttpPost]
            public async Task<IActionResult> Login(UserVm user)
            {
                var claims = new List<Claim>
                {
                    new Claim(ClaimTypes.Name, user.Username),
                    new Claim(ClaimTypes.Role, "Administrator")
                };
    
                var claimsIdentity = new ClaimsIdentity(
                    claims, CookieAuthenticationDefaults.AuthenticationScheme);
    
                var authProperties = new AuthenticationProperties
                {
                    //AllowRefresh = <bool>,
                    // Refreshing the authentication session should be allowed.
    
                    //ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(10),
                    // The time at which the authentication ticket expires. A 
                    // value set here overrides the ExpireTimeSpan option of 
                    // CookieAuthenticationOptions set with AddCookie.
    
                    IsPersistent = false,
                    // Whether the authentication session is persisted across 
                    // multiple requests. When used with cookies, controls
                    // whether the cookie's lifetime is absolute (matching the
                    // lifetime of the authentication ticket) or session-based.
    
                    //IssuedUtc = <DateTimeOffset>,
                    // The time at which the authentication ticket was issued.
    
                    //RedirectUri = <string>
                    // The full path or absolute URI to be used as an http 
                    // redirect response value.
                };
    
                await HttpContext.SignInAsync(
                    CookieAuthenticationDefaults.AuthenticationScheme,
                    new ClaimsPrincipal(claimsIdentity),
                    authProperties);
    
                return RedirectToAction("Index", "Home");
            }
        }

    Login View

    @{
        ViewData["Title"] = "Index";
    }
    
    <h1>Index</h1>
    
    <form asp-action="Login">
        <div>
            <input name="Username" type="text" value="FooBar" />
        </div>
        <div>
            <input id="Submit1" type="submit" value="submit" />
        </div>
    </form>

    Finally Home secured Home controller.

        [Authorize]
        public class HomeController : Controller
        {
            private readonly ILogger<HomeController> _logger;
    
            public HomeController(ILogger<HomeController> logger)
            {
                _logger = logger;
            }
    
            public IActionResult Index()
            {
                return View();
            }
    
            public IActionResult Privacy()
            {
                return View();
            }
    
            [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
            public IActionResult Error()
            {
                return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
            }
        }

    Friday, July 31, 2020 9:48 PM
  • User-462241089 posted

    I don't see any differences between my code and yours. Is there a setting that could be causing this?

    Sunday, August 2, 2020 8:59 PM
  • User475983607 posted

    I don't see any differences between my code and yours. Is there a setting that could be causing this?

    Developers run into head scratches all the time.  You job is to figure out if the problem is the code, your understanding, or an oversight.  For example, forgetting to add the [Authorize] attribute and thinking the cookie still exists because you can access resource that you thought were secured.

    The best thing to do is create a test that targets the problem.  That's exactly the purpose of the code I provided.  I login and can access secured resources.  If I close the browser I have to login again before accessing secured resources.  if you run my code and it works as expected then there is something wrong with your code.  It's your job to figure what's wrong with your code.  

    Sunday, August 2, 2020 9:57 PM
  • User-462241089 posted

    You're right, and I'll be testing a lot today. Thanks for your help, mgebhard.

    EDIT: I just ran some tests in other browser, and it appears the http cookie is not deleted if another window is open. So this must be something like a tab closing, then. I'll see if I can set it to delete the cookies when a tab is closed.

    Monday, August 3, 2020 2:11 PM
  • User753101303 posted

    Hi,

    With at least some browsers you have to close the whole browser not just the window.

    Monday, August 3, 2020 2:18 PM
  • User475983607 posted

    You're right, and I'll be testing a lot today. Thanks for your help, mgebhard.

    EDIT: I just ran some tests in other browser, and it appears the http cookie is not deleted if another window is open. So this must be something like a tab closing, then. I'll see if I can set it to delete the cookies when a tab is closed.

    Cookies are scoped to the browser not the tab.  You will not be able to delete an HTTP cookie when a tab is closed.   For one, JavaScript cannot access HTTP cookies.  Second, modern browsers ignore HTTP requests made when a tab closes as it it can hint the UI experience.  Even if there were a hack, how would you know when to delete the cookie?  You application would need to read the user's mind...

    The best advice I can give you is to learn the how things work.  In this case how HTTP cookies work.  

    Monday, August 3, 2020 2:58 PM
  • User-462241089 posted

    After reading this, I see what you mean about reading the user's mind.

    "

    • Session cookies are deleted when the current session ends. The browser defines when the "current session" ends, and some browsers use session restoring when restarting, which can cause session cookies to last indefinitely long.

    "

    I'll start researching some solution, but I'm starting to wonder if there is one.

    Monday, August 3, 2020 3:50 PM
  • User-462241089 posted

    PatriceSc, your right, it is just that I need to sign the user out when they close the tab or one window. I know I can sign them out with 

                    await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    

    So I guess I just need to code for this to trigger when they close the tab or window...

    Monday, August 3, 2020 4:01 PM
  • User475983607 posted

    MarcusAtMars

    After reading this, I see what you mean about reading the user's mind.

    "

    • Session cookies are deleted when the current session ends. The browser defines when the "current session" ends, and some browsers use session restoring when restarting, which can cause session cookies to last indefinitely long.

    "

    I'll start researching some solution, but I'm starting to wonder if there is one.

    Did you research Browser Session Restoring?  This is generally a feature the user must enable or request.  If the user wishes to restore a session then they can.  It's their browser and the server has no control over the user's browser.

    It is up to you as a developer to understand how these things work and write code that best secures the cookie for whatever you are trying to do.  Keep in mind, the authentication cookie contains a token which also has an expiration.  You can set the token expiration to a short time span or whatever makes sense. 

    Monday, August 3, 2020 4:07 PM
  • User-462241089 posted

    The cookie (or its token, I guess) need to last for an hour before they expire.

    I think I can add javascript to the app and window.onbeforeunload and send a request to the logout method in my controller. Not the best solution since a user can edit the javascript, but I'll try it anyway.

    Monday, August 3, 2020 5:04 PM
  • User475983607 posted

    MarcusAtMars

    The cookie (or its token, I guess) need to last for an hour before they expire.

    Then set the token to expire.  This is all configuration and totally up to you.

    services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
        .AddCookie(options => {
            options.LoginPath = "/Account/Index";
            options.ExpireTimeSpan = TimeSpan.FromHours(1);
            });

    MarcusAtMars

    I think I can add javascript to the app and window.onbeforeunload and send a request to the logout method in my controller. Not the best solution since a user can edit the javascript, but I'll try it anyway.

    Modern browsers ignore AJAX requests in the window.onbeforeunload event.  Plus the event fires every time the users clicks a link or a button.  The event is commonly used to warn user to save their work before leaving a page.

    Monday, August 3, 2020 6:22 PM
  • User-462241089 posted

    So after talking with you guys, trying myself for several hours, reading many articles, and even talking with the authors of the docs, it appears that is is impossible to code this behavior.

    The reason why is because javascript cannot "detect" a close tab or window event. If there was such a way, then I could code this behavior, but there isn't. I guess I will just have to love with this. The user's browser is the one who decides if the session is closed out or not, and this is just how browsers and http sessions work. Can't change it.

    This is an unforeseen caveat with cookie authentication that wasn't mentioned to me upfront, and I sort of wish it was. Hopefully, the doc authors will take my feedback to heart and will edit the docs to reflect this.

    Good luck to whoever stumbles upon this thread!

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Tuesday, August 4, 2020 1:18 PM
  • User475983607 posted

    MarcusAtMars

    The reason why is because javascript cannot "detect" a close tab or window event. If there was such a way, then I could code this behavior, but there isn't. I guess I will just have to love with this. The user's browser is the one who decides if the session is closed out or not, and this is just how browsers and http sessions work. Can't change it.

    This is an unforeseen caveat with cookie authentication that wasn't mentioned to me upfront, and I sort of wish it was. Hopefully, the doc authors will take my feedback to heart and will edit the docs to reflect this.

    I think it is important to mention that web sites have always been stateless.  It is a well-known fact that HTTP is s stateless protocol.  What's happened here is you just realized what stateless means.  It is a coincidence you figured this out with an Authentication Cookie.

    Tuesday, August 4, 2020 2:34 PM