9

I'm currently building a C# Net Core 2.2 app that is using Azure B2C OIDC for login/authentication. I've customized the login page and know how to customize the edit/forgot password screens with CSS and code hosted on my site using custom page layouts.

The problem I'm running into is that on signout, I'm being redirected to /AzureADB2C/Account/SignOut. I'd like to either modify the CSS like I can with the login page, or change that URL to go to a custom controller action hosted on my site.

Does anyone know how/what the process is to manage that? It seems weird they would have custom layouts available for everything "but" the sign out process.

As a workaround, I found I could add a "Rewrite Option" for handling the SignOut URL and rewriting it to a controller I have on my site. However, I'm not sure if this is the optimal way to accomplish this task, it was on a very obscure MSDN page, but it does work. See below:

// Inside Startup.cs
// Workaround for SignedOut URL error in MSFT code 
RewriteOptions rewrite = new RewriteOptions().AddRedirect("AzureADB2C/Account/SignedOut","Account/SignedOut"); 
app.UseRewriter(rewrite);
Ian Nielson
  • 103
  • 1
  • 4

7 Answers7

5

If you look at the source code of the AccountController in the Microsoft.AspNetCore.Authentication.AzureADB2C.UI nuget package, you can see that the callbackUrl is hard-coded to (/AzureADB2C)/Account/SignedOut.

But there is no requirement to use that controller. Just call your own SignOut action on your own controller. Copy-paste the code from the AzureADB2C SignOut action and change the callbackUrl to your own.

Edit _LoginPartial.cshtml: remove asp-area="AzureADB2C" and use your own for asp-controller and asp-action.

Marcel Wolterbeek
  • 3,367
  • 36
  • 48
  • That means when using Blazor we should create an AccountController only for this requirement. I will try and go the RewriteOption way. – Sven Jan 17 '20 at 13:39
  • @Sven , you can do it without a controller, just posted my notes on how to change the callback URL – Mechanical Object Apr 24 '20 at 21:02
3

[Additional information to the answer provided by @Marcel W and to the question asked by @Sven]

A bit late to the party but in case it helps others :

  • Blazor server app .net core 3.1
  • Authentication : Azure B2C

Original code is in the following repository

You'll see that in signout method the callback url is unfortunately hard coded.

        [HttpGet("{scheme?}")]
        public async Task<IActionResult> SignOut([FromRoute] string scheme)
        {
            scheme = scheme ?? AzureADB2CDefaults.AuthenticationScheme;
            var authenticated = await HttpContext.AuthenticateAsync(scheme);
            if (!authenticated.Succeeded)
            {
                return Challenge(scheme);
            }

            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.AllSchemes);
        }

So the idea is to take the code and create the same behavior in the project.

  1. Create Areas folder
  2. Create AzureADB2C folder inside Areas folder
  3. Create Pages folder inside AzureADB2C folder
  4. Create Account folder inside Pages folder
  5. Create SignOut.cshtml file inside Account folder
  6. Copy/Paste following code

@page
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Authentication.AzureADB2C.UI
@using Microsoft.Extensions.Options
@inject IOptionsMonitor<AzureADB2COptions> Options
@attribute [IgnoreAntiforgeryToken]
@functions {
    public async Task<IActionResult> OnPost([FromRoute] string scheme)
    {
        scheme = scheme ?? AzureADB2CDefaults.AuthenticationScheme;
        var authenticated = await HttpContext.AuthenticateAsync(scheme);
        if (!authenticated.Succeeded)
        {
            return Challenge(scheme);
        }

        var options = Options.Get(scheme);

        var callbackUrl = Url.Page("/", pageHandler: null, values: null, protocol: Request.Scheme);
        return SignOut(
            new AuthenticationProperties { RedirectUri = callbackUrl },
            options.AllSchemes);
    }
}

The final modification will take place in LoginDisplay.razor file. We need to create a form that'll do our "post" in order to sign off the user

Replace the following line in this file

    <a href="AzureADB2C/Account/SignOut">Log out</a>

by

    <form method="post" action="AzureADB2C/Account/SignOut">
        <button type="submit" class="nav-link btn btn-link">Log out</button>
    </form>

Below a screenshot that illustrates the directory structure

enter image description here

2

I presume you're using the User Fows (policies) to customize your Signin/Profile editing/Password reset pages. You may notice that there's no Signout user flow, so you cannot do anything here about it.

But MS gives you another way to have your own post-logout page. When you logout from your web app you should redirect to B2C's logout endpoint as described here. (Note: that's what you should do anyway, even if you don't want a custom logout page)

When you want to sign the user out of the application, it isn't enough to clear the application's cookies or otherwise end the session with the user. Redirect the user to Azure AD B2C to sign out. If you fail to do so, the user might be able to reauthenticate to your application without entering their credentials again.

The logout endpoint can receive an optional post_logout_redirect_uri parameter in the query string, where you can specify another URL where your user will be finally redirected by B2C. That can be the address of any resource, e.g. you homepage or your own page showing a "You successfully logged out of our service" message to the user.

post_logout_redirect_uri - The URL that the user should be redirected to after successful sign out. If it isn't included, Azure AD B2C shows the user a generic message.

Vic
  • 442
  • 2
  • 12
1

My solution based on the above answer

On your controller

Copy-paste the function of SignOut from Source Code

You need to Inject the IOptionsMonitor _azure (in my case)

 [HttpGet("logout/{scheme?}")]
    public async Task<IActionResult> SignOut([FromRoute] string scheme)
    {
        scheme = scheme ?? AzureADB2CDefaults.AuthenticationScheme;
        var authenticated = await HttpContext.AuthenticateAsync(scheme);
        if (!authenticated.Succeeded)
        {
            return Challenge(scheme);
        }

        var options = _azure.Get(scheme);

        var callbackUrl = "/";
        return SignOut(
            new AuthenticationProperties { RedirectUri = callbackUrl },
            options.AllSchemes);
    }

After that, you just need to call the Controller from your Views or FrontEnd

cryptixh
  • 11
  • 3
0

Currently we can not customize the sign out UI directly by using custom page layouts.

To use the RewriteOptions() method is an optional way for you. Or you can just build your own AccountController instead of using the default one shipped with ASP.NET CORE. Their principles are the same.

Tony Ju
  • 14,891
  • 3
  • 17
  • 31
0

according to https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/127 try this (worked for me):

For MSA accounts, the logout page should redirect back to your app if:

User has used/consented to the client app

Logout uri is https

Logout uri is registered as a reply uri in the portal

Logout uri is registered as the post logout url

Logout uri is set as auth.postLogoutRedirectUri in msal (and you call logout).

Miroslav Siska
  • 401
  • 4
  • 15
0

After looking at the library at github - as already linked in other answer - I ended up with the following:

@if (ViewData["Title"].Equals("Signed out"))
{
    <div class="content">Abgemeldet!</div>
}
@string mainStyle = ViewData["Title"].Equals("Signed out") ? "display:none;" : string.Empty;
<main style=@mainStyle>
     @RenderBody()
</main>

I know.. a -little- tightly bound to a string constant inside a library ... but very practical...

Martin Meeser
  • 2,784
  • 2
  • 28
  • 41