0

I'm build an ASP.Net Core page with MicrosoftIdentity login/logout.

My _LoginPartial looks like this:

  1. If the user is not signed in, it shows a "Sign in" item
  2. If the user is signed in, it shows their initials with a dropdown, then their name, email, Sign Out and Switch Accounts buttons
@using System.Security.Principal
@using System.Security.Claims;

<ul class="navbar-nav">
@if (User.Identity?.IsAuthenticated == true)
{

        <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" id="navbarDarkDropdownMenuLink" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                @string.Concat((User.Claims.FirstOrDefault(c => c.Type == "name")?.Value ?? "")
                .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
                .Where(x => x.Length >= 1 && char.IsLetter(x[0]))
                .Select(x => char.ToUpper(x[0])))
            </a>
            <div class="dropdown-menu  dropdown-menu-right" aria-labelledby="navbarDarkDropdownMenuLink">
                <div class="dropdown-item-text"><b>@User.Claims.FirstOrDefault(c => c.Type == "name")?.Value</b></div>
                <div class="dropdown-item-text">@User.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value</div>
                <div class="dropdown-divider"></div>
                <a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignOut" asp-route-postLogoutRedirectUri=@ViewContext.HttpContext.Request.Path>Sign out</a>
                <a class="nav-link text-dark" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn" [email protected]>Switch Account</a>
            </div>
        </li>

}
else
{
        <li class="nav-item">
            <a class="nav-link" asp-area="MicrosoftIdentity" asp-controller="Account" asp-action="SignIn" [email protected]>Sign in</a>
        </li>
}
</ul>

The "Switch Account" was achieved by adding "Prompt": "select_account" to my appconfig AzureAd section and works great.

The asp-route-redirectUri parameters work great for the "Sign in" and "Switch Account" actions, but they don't seem to be used by Sign Out. I've tried with asp-route-redirectUri and asp-route-postLogoutRedirectUri but neither work. Is there a different name for a post-logout redirect?

I've tried using the Rewriter, which works if I'm redirecting to a specific page:

            app.UseRewriter(
                new RewriteOptions().Add(context =>
                {
                    if (context.HttpContext.Request.Path == "/MicrosoftIdentity/Account/SignedOut")
                    {
                        context.HttpContext.Response.Redirect("/Home/Index");
                    }
                }));

But at the point that's invoked, I can't find anything in the context to tell me where it originally came from (and hence to redirect back to).

So, what can I add to the original link anchor:

<a class="nav-link text-dark" 
   asp-area="MicrosoftIdentity" 
   asp-controller="Account" 
   asp-action="SignOut" 
   asp-route-postLogoutRedirectUri=@ViewContext.HttpContext.Request.Path
>
   Sign out
</a>

that will show up in the final post-logout context, that I can use to decide where to redirect to (i.e. staying on the original page)?

Or is there a simpler way for me to have a "Sign out" button on my page that returns to the same page after the log out flow has completed?

2 Answers 2

0

From the documentation we can know that by default, the logout URL displays the signed-out view page SignedOut.cshtml.cs. This page is also provided as part of MIcrosoft.Identity.Web, so it cannot be obtained directly in this case Want to return the postLogoutRedirectUri, and you can see that it will redirect to the signout page, so you can try to override Microsoft.Identity.Web by customizing the SignedOut.cshtml page and placing it under the Areas/MicrosoftIdentity/Pages/Account/ path . And on this basis, obtain the current postLogoutRedirectUri to be returned for redirection.

0

Thanks to @yuning-duan for the pointer, I was finally able to get it working using a custom AccountController.cs

My _LoginPartial is:

@using System.Security.Principal
@using System.Security.Claims;

@{string BackToMe = ViewContext.HttpContext.Request.Path + ViewContext.HttpContext.Request.QueryString;}

<ul class="navbar-nav">
@if (User.Identity?.IsAuthenticated == true)
    {
        <li class="nav-item dropdown">
            <a class="nav-link dropdown-toggle" href="#" id="navbarDarkDropdownMenuLink" 
                role="button" data-bs-toggle="dropdown" aria-expanded="false">
                @string.Concat((User.Claims.FirstOrDefault(c => c.Type == "name")?.Value ?? "")
                         .Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)
                         .Where(x => x.Length >= 1 && char.IsLetter(x[0]))
                         .Select(x => char.ToUpper(x[0])))
            </a>
            <div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDarkDropdownMenuLink">
                <div class="dropdown-item-text"><b>@User.Claims.FirstOrDefault(c => c.Type == "name")?.Value</b></div>
                <div class="dropdown-item-text">@User.Claims.FirstOrDefault(c => c.Type == "preferred_username")?.Value</div>
                <div class="dropdown-divider"></div>
                <a class="dropdown-item" asp-controller="Account" asp-action="SignOut"
                   asp-route-redirectUri=@BackToMe>Sign out</a>
                <a class="dropdown-item" asp-controller="Account" asp-action="SignIn"
                   asp-route-redirectUri=@BackToMe>Switch Account</a>
            </div>
        </li>
    }
    else
    {
        <li class="nav-item">
            <a class="nav-link" asp-controller="Account" asp-action="SignIn"
               asp-route-redirectUri=@BackToMe>Sign in</a>
        </li>
    }
</ul>

The 'Switch Account' action is achieved by adding

"Prompt": "select_account"

to the AzureAd config in appsettings.json

And I have an AccountController.cs as:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;

namespace MySite.Controllers
{
    [AllowAnonymous]
    public class AccountController : Controller
    {
        [HttpGet]
        public IActionResult SignIn()
        {
            return Challenge(
                new AuthenticationProperties { RedirectUri = GetRedirectUrl() },
                OpenIdConnectDefaults.AuthenticationScheme);
        }


        [HttpGet]
        public new async Task<IActionResult> SignOut()
        {
            var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: new { redirectUri = GetRedirectUrl() }, protocol: Request.Scheme);
            var properties = new AuthenticationProperties { RedirectUri = GetRedirectUrl() };
            //obtain the id_token
            var idToken = await HttpContext.GetTokenAsync("id_token");
            //send the id_token value to the authentication middleware
            properties.Items["id_token_hint"] = idToken;

            return SignOut(
                properties,
                CookieAuthenticationDefaults.AuthenticationScheme,
                OpenIdConnectDefaults.AuthenticationScheme);
        }

        [HttpGet]
        public IActionResult SignedOut()
        {
            return Redirect(GetRedirectUrl());
        }

        [HttpGet]
        public IActionResult AccessDenied()
        {
            return View();
        }

        private string GetRedirectUrl()
        {
            string redirectUrl = Request.Query["redirectUri"].ToString();
            return string.IsNullOrEmpty(redirectUrl) ? "/" : redirectUrl;
        }
    }
}

(I'm not sure if I need that idToken bit in the Sign-out, but it seems to sometimes return to the page and sometimes not, depending on the account type - Microsoft Personal account or Work/School Account)

Not the answer you're looking for? Browse other questions tagged or ask your own question.