Why does the end user have to log out twice?
In the Account/Logout
page, which lives under Areas/Identity/Account/Logout.cshtml.cs
in your scaffolded ASP.NET Core Identity code, there is an OnGet
handler that looks like this:
public void OnGet() { }
Because this is using ASP.NET Core Razor Pages, all this does is render the corresponding Logout.cshtml
page. In your example, when you hit Logout
in the MVC app, it clears its own cookies and then passes you over to the IS4 app (the OnGet
, specifically). Because this OnGet
handler is empty, it's not really doing anything and it's certainly not signing you out of the IS4 app.
If you look at the OnPost
handler inside of Logout.cshtml.cs
, you'll see it looks something like this:
public async Task<IActionResult> OnPost(string returnUrl = null)
{
await _signInManager.SignOutAsync();
// ...
}
This call to SignOutAsync
does exactly what it suggests: it signs you out of IS4 itself. However, in your current workflow, this OnPost
handler is not being called. The OnGet
handler is called indirectly when you use Logout
in the MVC app, as I've already mentioned.
Now, if you look at the controller/action implementation of IS4 logout in the Quickstart.UI project, you'll see that essentially it passes the GET
request over to the POST
request. Here's the code, with comments stripped out:
[HttpGet]
public async Task<IActionResult> Logout(string logoutId)
{
var vm = await BuildLogoutViewModelAsync(logoutId);
if (vm.ShowLogoutPrompt == false)
return await Logout(vm);
return View(vm);
}
When logging out, there's a setting that controls whether or not the user should first be prompted to confirm whether or not they want to log out. That's mostly what this code is taking care of - it passes it straight over to the POST
request handler if the prompt is not required. Here's a snippet of the code for the POST
:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Logout(LogoutInputModel model)
{
var vm = await BuildLoggedOutViewModelAsync(model.LogoutId);
if (User?.Identity.IsAuthenticated == true)
{
await HttpContext.SignOutAsync();
// ...
}
// ...
return View("LoggedOut", vm);
}
The important line here is the call to HttpContext.SignOutAsync
- this ends up removing the cookie that IS4 is using to keep you signed in. Once this has been removed, you're signed out of IS4. Ultimately, this is what is missing from your current implementation.
At the simplest level, you can fix your issue by updating your OnGet
to look like this:
public async Task<IActionResult> OnGet()
{
if (User?.Identity.IsAuthenticated == true)
{
await _signInManager.SignOutAsync();
return RedirectToPage(); // A redirect ensures that the cookies has gone.
}
return Page();
}
This doesn't support the ShowLogoutPrompt
option I've detailed above, simply just to keep this answer a little bit shorter. Apart from that, it's just using _signInManager
to do the logout given that you're in the ASP.NET Core Identity world.
I encourage you to explore the full source-code from the Quickstart.UI implementation in order to support ShowLogoutPrompt
, returnUrl
, etc - I can't possibly do that here without writing a book.