Search code examples
c#asp.netrazorasp.net-coreasp.net-core-2.2

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


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.


Solution

  • 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