.Net 8: Cannot logout user with "Individual Accounts Auth" template

50 views Asked by At

I created a web app from scratch using the default .Net template for MVC (ASP.NET Core web app) using Auth with "Individual Accounts" option and SDK 8, and my problem is I'm not able to logout a user (I found it also very problematic to login, but ended solving that problem).

Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

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

builder.Services.AddControllersWithViews();
builder.Services.AddScoped<IAWSService, AWSService>();
builder.Services.AddScoped<IAWSRepo, AWSRepo>();
builder.Services
            .Configure<Settings>(builder.Configuration)
            .AddSingleton(sp => sp.GetRequiredService<IOptions<Settings>>().Value);

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
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.UseAuthorization();
app.UseAuthentication();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();

app.Run();

Login.cshtml.cs:

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    if (ModelState.IsValid)
    {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        //var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);

        var result = SignInResult.Failed;
        var aws = new AWS_Manager(TestController.access_id, TestController.secret_key);

        UserModel oUser = await Task.Run(() => aws.getUser(Input.Email));
        if (oUser != null)
        {
            if (oUser.Password == Crypto.GetSHA1(Input.Password)) // && oUser.testloader_enabled == true)
            {
                //A claim is a statement about a subject by an issuer and    
                //represent attributes of the subject that are useful in the context of authentication and authorization operations.    
                var claims = new List<Claim>() {
                            // adding following 2 claim just for supporting default antiforgery provider
                            new Claim(ClaimTypes.NameIdentifier, Input.Email),
                            new Claim("http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider",
                            "ASP.NET Identity", "http://www.w3.org/2001/XMLSchema#string"),
                            new Claim(ClaimTypes.Name, Input.Email),

                            // optionally you could add roles if any
                            // new Claim(ClaimTypes.Role, "RoleName"),
                            // new Claim(ClaimTypes.Role, "AnotherRole"),
                };
                //Initialize a new instance of the ClaimsIdentity with the claims and authentication scheme    
                var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                //Initialize a new instance of the ClaimsPrincipal with ClaimsIdentity    
                var principal = new ClaimsPrincipal(identity);
                //SignInAsync is a Extension method for Sign in a principal for the specified scheme.    
                await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
                {
                    IsPersistent = Input.RememberMe
                });
                return LocalRedirect(returnUrl);
            }
        }

        if (result.RequiresTwoFactor)
        {
            return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
        }
        if (result.IsLockedOut)
        {
            _logger.LogWarning("User account locked out.");
            return RedirectToPage("./Lockout");
        }
        else
        {
            ModelState.AddModelError(string.Empty, "Invalid login attempt.");
            return Page();
        }
    }

    // If we got this far, something failed, redisplay form
    return Page();
}

_LoginPartial.cshtml:

public class LogoutModel : PageModel
{
    private readonly SignInManager<IdentityUser> _signInManager;
    private readonly ILogger<LogoutModel> _logger;

    public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
    {
        _signInManager = signInManager;
        _logger = logger;
    }

    public async Task<IActionResult> OnPost(string returnUrl = null)
    {
        await _signInManager.SignOutAsync();
        _logger.LogInformation("User logged out.");
        if (returnUrl != null)
        {
            return LocalRedirect(returnUrl);
        }
        else
        {
            // This needs to be a redirect so that the browser performs a new
            // request and the identity for the user gets updated.
            //return RedirectToPage();
            return LocalRedirect(returnUrl);
        }
    }
}

Logout.cshtml:

@page
@model LogoutModel
@{
    ViewData["Title"] = "Log out";
}

<header>
    <h1>@ViewData["Title"]</h1>
    @{
        if (User.Identity?.IsAuthenticated ?? false)
        {
            <form class="form-inline" asp-area="Identity" asp-page="/Account/Logout" asp-route-returnUrl="@Url.Page("/", new { area = "" })" method="post">
                <button type="submit" class="nav-link btn btn-link text-dark">Click here to Logout</button>
            </form>
        }
        else
        {
            <p>You have successfully logged out of the application.</p>
        }
    }
</header>

Logout.cshtml.cs:

public class LogoutModel : PageModel
{
    private readonly SignInManager<IdentityUser> _signInManager;
    private readonly ILogger<LogoutModel> _logger;

    public LogoutModel(SignInManager<IdentityUser> signInManager, ILogger<LogoutModel> logger)
    {
        _signInManager = signInManager;
        _logger = logger;
    }

    public async Task<IActionResult> OnPost(string returnUrl = null)
    {
        await _signInManager.SignOutAsync();
        _logger.LogInformation("User logged out.");
        if (returnUrl != null)
        {
            return LocalRedirect(returnUrl);
        }
        else
        {
            // This needs to be a redirect so that the browser performs a new
            // request and the identity for the user gets updated.
            //return RedirectToPage();
            return LocalRedirect(returnUrl);
        }
    }
}

The breakpoint I've set in OnPost in Logout.cshtml.cs is hit, but apparently await _signInManager.SignOutAsync(); is not working, as User.Identity.IsAuthenticated keeps returning true.

What can I try next?

1

There are 1 answers

0
Brando Zhang On

According to your codes, you are using the cookie authentication actually not the identity inside your login page, so you use _signInManager will not log out the cookie authentication cookie.

Inside the Login.cshtml.cs, you use below codes:

            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties()
            {
                IsPersistent = Input.RememberMe
            });

So if you want to logout the user, you should use below codes not the _signInManager.SignOutAsync() :

// Clear the existing external cookie
await HttpContext.SignOutAsync(
    CookieAuthenticationDefaults.AuthenticationScheme);