Blazor Azure AD B2C Custom URL

BLAZOR AND AZURE AD B2C: OVERRIDE CONTROLLER FROM CUSTOM URL

by

In this tutorial, you will learn how to override AccountController.cs in server-side Blazor so you can set a custom SignedOut URL when using Azure AD B2C authentication. If you have created a Blazor app with Azure AD B2C authentication, it was likely scaffolded using the helpful AzureADB2C.UI package. Unfortunately, since this is now included as a class library .dll, it takes a little effort to customize the routes.

By default, your auth-enabled Blazor web app will use routes such as AzureADB2C/Account/SignIn. Moreover, when a user logs out, they are taken to an unbranded page located at /AzureADB2C/Account/SignedOut. Both of these are hard-coded into the AzureADB2C.UI package’s AccountController.cs (see source code), but neither provide for a great user experience in a public-facing web app.

Disable existing Account Controller

To override the Account controller, you must first disable the existing AccountController. To do this, create a new class file in the root of your Blazor project and call it B2CExtensions.cs. Then, create a method to remove AzureADB2C.UI’s Account controller feature provider from the list of controller feature providers. The entire class might look like the following.

using Microsoft.Extensions.DependencyInjection;
using System.Linq;

namespace BlazorAzureADB2C
{
    public static class B2CExtensions
    {
        public static IMvcBuilder RemoveADB2CAccountController(this IMvcBuilder builder)
        {
            var ADB2CAccountController = builder.PartManager.FeatureProviders.FirstOrDefault(fp => fp.ToString()
                .Equals("Microsoft.AspNetCore.Authentication.AzureADB2C.UI.AzureADB2CAccountControllerFeatureProvider"));
            if (ADB2CAccountController != null)
            {
                builder.PartManager.FeatureProviders.Remove(ADB2CAccountController);
            }
            return builder;
        }
    }
}

The RemoveADB2CAccountController() method allows your app to avoid loading the default Account controller feature from the assembly. There is no need to also remove the compiled views provided by the Nuget package, because you can still use those with your custom controller.

Next, in your Blazor project’s Startup.cs file, invoke the method you just created. Within the ConfigureServices() block, you can simply chain it onto the AddRazorPages() call.

services.AddRazorPages()
    .RemoveADB2CAccountController();

Depending on your project’s configuration, you could also chain the RemoveADB2CAccountController() call onto the AddMvc() or AddControllers() methods.

Customize AccountController.cs

Now it is time to create the new Account controller. Start by creating a Controllers folder in your Blazor project, and add a new AccountController.cs class. Feel free to use the source code for the existing controller as a guide.

I wanted my new controller to be able to use the options I set up when configuring the project to use my Azure AD B2C tenant, so I included the package’s namespace.

using Microsoft.AspNetCore.Authentication.AzureADB2C.UI;

Next, I did not like AzureADB2C included in the URL for my application’s user flows, such as sign-in and sign-out, so I changed the route. The previous code is commented out in the example below.

namespace BlazorAzureADB2C.Controllers
{
    //[NonController]
    //[Area("AzureADB2C")]
    //[Route("[area]/[controller]/[action]")]
    [AllowAnonymous]
    [Route("[controller]/[action]")]
    public class AccountController : Controller
    {
        ...
    }
}

Now, the sign-in action will appear at Account/SignIn instead of AzureADB2C/Account/SignIn. Don’t forget to also update the links in Shared/LoginDisplay.razor.

Finally, I wanted users to be redirected to the home page upon successful sign-out instead of loading /AzureADB2C/Account/SignedOut. My new SignOut action looks like the following:

[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);
    var callbackUrl = Url.Page("/", pageHandler: null, values: null, protocol: Request.Scheme);
    return SignOut(
        new AuthenticationProperties { RedirectUri = callbackUrl },
        options.AllSchemes);
}

Add Controller to Blazor

The only thing left to do is to make sure your new controller is loaded into the project. Go back into Startup.cs and locate the Configure() method. Add controller action endpoints by calling the MapControllers() extension for the Endpoints middleware.

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllers();
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

The Bottom Line

Test your application by attempting to login and logout. For an app running on port 5000, you should be able to activate the SignIn userflow at http://localhost:5000/Account/SignIn. When you log out, you should be taken to your applications home page (or whatever location you configured in the controller). To test whether the previous controller was successfully disabled, make sure nothing is available at the old action locations, such as /AzureADB2C/Account/SignIn. That’s it! Feel free to make any other customizations to your AccountController that you see fit. Let me know in the comments if this tutorial helped you.


Don't stop learning!

There is so much to discover about C#. That's why I am making my favorite tips and tricks available for free. Enter your email address below to become a better .NET developer.


Did you know?

Our beautiful, multi-column C# reference guides contain more than 150 tips and examples to make it even easier to write better code.

Get your cheat sheets