HttpContext.SignOutAsync() neither logs out the user nor deletes the local cookie

Viewed 4018

I know there are already questions about this topic but none of the given answers worked in my situation.

Here's the core:

Startup.cs

public void ConfigureServices(IServiceCollection services)
    {
        services.AddDbContext<comedyContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.Configure<CookiePolicyOptions>(options =>
        {
            // This lambda determines whether user consent for non-essential cookies is needed for a given request.
            options.CheckConsentNeeded = context => true;
            options.MinimumSameSitePolicy = SameSiteMode.None;
        });

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

        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/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.UseCookiePolicy();
        app.UseAuthentication();

        app.UseMvc();
    }

Login.cshtml.cs

    public class LoginModel : PageModel
{
    [BindProperty]
    public string inputUsername { get; set; }
    [BindProperty]
    public string inputPassword { get; set; }

    private readonly comedyContext _context;

    public LoginModel(comedyContext context)
    {
        _context = context;
    }

    public async Task<IActionResult> OnPostAsync()
    {
        var user = await _context.User.FirstOrDefaultAsync(u =>
            string.Equals(u.Username, inputUsername, StringComparison.CurrentCultureIgnoreCase) && string.Equals(u.Password, Utility.sha256_hash(inputPassword), StringComparison.CurrentCultureIgnoreCase));

        if (user is null)
            return Redirect("/login");

        var claims = new List<Claim>
        {
            new Claim(ClaimTypes.NameIdentifier, user.UserId.ToString()),
            new Claim(ClaimTypes.Name, inputUsername)
        };

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

        var authProperties = new AuthenticationProperties
        {
            AllowRefresh = true,
            // Refreshing the authentication session should be allowed.

            ExpiresUtc = DateTimeOffset.UtcNow.AddHours(24),
            // The time at which the authentication ticket expires. A 
            // value set here overrides the ExpireTimeSpan option of 
            // CookieAuthenticationOptions set with AddCookie.

            IsPersistent = true,
            // Whether the authentication session is persisted across 
            // multiple requests. Required when setting the 
            // ExpireTimeSpan option of CookieAuthenticationOptions 
            // set with AddCookie. Also required when setting 
            // ExpiresUtc.

            IssuedUtc = DateTimeOffset.UtcNow,
            // 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(userIdentity), authProperties);

        //Just redirect to our index after logging in. 
        return Redirect("/dashboard");
    }

    public void OnGet()
    {
    }
}

Dashboard.cshtml

@page
@using System.Security.Claims
@using Microsoft.AspNetCore.Identity
@using ComedyWebsite.Controllers
@model ComedyWebsite.Pages.DashboardModel
@{
}

Hello @User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name)?.Value

<form asp-controller="Logout" asp-action="Logout" method="post" 
id="logoutForm">
    <button type="submit">Logout</button>
</form>

LogoutController.cs

public class LogoutController : Controller
{
    [HttpPost]
    public async Task<IActionResult> Logout()
    {
        // SomeOtherPage is where we redirect to after signout
        await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        HttpContext.Response.Cookies.Delete($".AspNetCore.{CookieAuthenticationDefaults.AuthenticationScheme}");
        return Redirect("/");
    }
}


As the image below shows, after clicking the Logout button the cookie still exists in the user's browser proof

I tried the solution given here at https://stackoverflow.com/questions/46131517/asp-net-core-identity-2-0-signoutasync but that didn't work either.

2 Answers

Not exactly what you're asking for, but maybe this approach will help.

I'm using Asp.Net Core 2.2 and have the below Logout() method in the Account Controller. I use the Identity service and go that route to logout rather than HttpContext. For me, this solution works and removes the sign-on cookie -- at least it did when I checked it a few minutes ago.

It differs from your code in that I'm using a signInManager object rather than an HttpContext object and this SignOutAsync() takes no parameters.

Try that, if you like, and see what you get. Be sure to call AddIdentity() or variations thereof in your startup.cs ConfigureServices method.

enter image description here

For your current code, you combine MVC Controller and Razor Page in the same project, but you did not configure any route for MVC Controller.

First, check the generated html for Logout form, make sure it generates like below:

<form method="post" id="logoutForm" action="/Logout/Logout">
    <button type="submit">Logout</button>
<input name="__RequestVerificationToken" type="hidden" value="xxx"></form>

If not, configure your mvc route like

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

Here is a working demo TestCookiesAuth

Related