How to specify custom logout URL when using Azure AD authentication in .NET core

Viewed 8883

I have an ASP.NET core 2.2 web application that uses work or school accounts (Azure AD authentication). When I sign out, the application ends up at

/AzureAD/Account/SignedOut

I'd like for it to redirect back to the home page using the Logout URL specified in the application registration. See below for screenshot. When specifying a logout URL here, Azure AD does in fact call that page (to clear session data), but then it finally ends up at the /AzureAD/Account/SignedOut location. I don't see anywhere else to specify the equivalent of a logout URL. Here is the code for the sign out button as generated by Visual Studio when using Azure AD authentication.

<a asp-area="AzureAD" asp-controller="Account" asp-action="SignOut">Sign out</a>

I've also tried adding the redirect directly onto the action.

<a asp-area="AzureAD" asp-controller="Account" asp-route-post_logout_redirect_uri="https://localhost:44381" asp-action="SignOut">Sign out</a>

enter image description here

3 Answers

One way is to use custom URL Rewriting Middleware to redirect by checking the path , put below codes before app.UseMvc:

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

The issue happens because the embeded AccountController.cs in ASP.NET core returns to the URL you mentioned:

        [HttpGet("{scheme?}")]
        public IActionResult SignOut([FromRoute] string scheme)
        {
            scheme = scheme ?? AzureADDefaults.AuthenticationScheme;
            var options = Options.Get(scheme);
            var callbackUrl = Url.Page("/Account/SignedOut", pageHandler: null, values: null, protocol: Request.Scheme);
            return SignOut(
                new AuthenticationProperties { RedirectUri = callbackUrl },
                options.CookieSchemeName,
                options.OpenIdConnectSchemeName);
        }

A workaround is to build you own AccountController instead of using the default one shipped with ASP.NET CORE, like below:

 public class AccountController : Controller
    {
        [HttpGet]
        public IActionResult SignIn()
        {
            var redirectUrl = Url.Action(nameof(HomeController.Index), "Home");
            return Challenge(
                new AuthenticationProperties { RedirectUri = redirectUrl },
                OpenIdConnectDefaults.AuthenticationScheme);
        }

        [HttpGet]
        public IActionResult SignOut()
        {
            var callbackUrl = Url.Action(nameof(SignedOut), "Account", values: null, protocol: Request.Scheme);
            return SignOut(
                new AuthenticationProperties { RedirectUri = callbackUrl },
                CookieAuthenticationDefaults.AuthenticationScheme,
                OpenIdConnectDefaults.AuthenticationScheme);
        }

        [HttpGet]
        public IActionResult SignedOut()
        {
            if (User.Identity.IsAuthenticated)
            {
                // Redirect to home page if the user is authenticated.
                return RedirectToAction(nameof(HomeController.Index), "Home");
            }

            return RedirectToAction(nameof(HomeController.Index), "ThePathYouWant");
        }

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

I agree with Tom. Here is my .NET 6 workaround:

[AllowAnonymous]
    [Area("MicrosoftIdentity")]
    [Route("[area]/[controller]/[action]")]
    public class B2CAccountController : Controller
    {

        /// <summary>
        /// Called when the Sign-In button is invoked (not after authenticated)
        /// </summary>
        /// <param name="scheme"></param>
        /// <returns></returns>
        [HttpGet("{scheme?}")]
        public IActionResult SignIn([FromRoute] string scheme)
        {
            scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
            var redirectUrl = Url.Content("~/");
            var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
            return Challenge(properties, scheme);
        }

        [HttpGet("{scheme?}")]
        public async Task<IActionResult> SignOutAsync([FromRoute] string scheme)
        {
            var redirectUrl = Url.Content("~/"); // CO 5/13 required to ensure logout redirects home
            var properties = new AuthenticationProperties { RedirectUri = redirectUrl };
            scheme ??= OpenIdConnectDefaults.AuthenticationScheme;
            //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, scheme);
        }
    }

I added a comment in the SyncOutAsync method that shows where to change redirect path.

Related