8

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

Geekn
  • 2,650
  • 5
  • 40
  • 80

3 Answers3

19

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"); }
        })
);
J Weezy
  • 3,507
  • 3
  • 32
  • 88
Nan Yu
  • 26,101
  • 9
  • 68
  • 148
  • 1
    It pains me that there's not a better solution, but this type of approach seems to be the easiest way to fix this. – philw Sep 20 '19 at 09:04
  • Above answer worked for me, however I needed to update the `context.HttpContext.Request.Path` to check `/MicrosoftIdentity/Account/SignedOut` – Melissa Aug 05 '21 at 13:24
  • @Melissa I have corrected the code. Thanks for pointing that out. – J Weezy Jan 07 '22 at 22:23
  • @nanyu This seems to be the simplest (yet safe) approach to handling user sign-out. It would be better if Microsoft could allow for us to pass in the preferred callback URL. Other solutions provided by Microsoft, such as; using the API to send requests, seem more complicated than should be necessary for what should be a relatively simple and straight forward task of signing a user out of an application end-to-end. – J Weezy Jan 07 '22 at 23:03
5

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();
        }
    }
Tom Luo
  • 602
  • 3
  • 10
  • Creating the custom AccountController for each method I get ''AccountController.SignOut()' hides inherited member 'ControllerBase.SignOut()'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword.' It works but can that warning be suppressed through code? – Irish Redneck Dec 28 '22 at 13:53
3

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.