How to integrate Kentico 13 Xperience Core MVC with Single Sign-On (SSO) using Azure AD B2C?

Introduction

Integrate Azure AD B2C SSO with Kentico Xperience Core MVC for seamless user experiences and enhanced security. This comprehensive guide provides step-by-step instructions, best practices, and considerations. Learn to streamline authentication processes and achieve centralized control. Empower your applications with SSO using Azure AD B2C!


 

Prerequisites:

  • Azure Subscription
  • Azure Active Directory
  • Azure B2C
  • nuGet Package
  • Microsoft.Identity.Web(2.5.0)
  • Microsoft.Identity.Web.UI(2.5.0)

 
To establish a connection between Azure AD B2C and your Kentico 13 Xperience Core project, follow these steps:
 
Step 1: Install the necessary NuGet packages in your project:
 
Microsoft.Identity.Web (version 2.5.0)
Microsoft.Identity.Web.UI (version 2.5.0)
 
Step 2: Add the following code snippet to your Startup.cs file:
 
// Code snippet for establishing connection to Azure AD B2C
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddInMemoryTokenCaches();
 
By implementing these steps, you can successfully establish the connection between Azure AD B2C and your Kentico 13 Xperience Core project.
 

private void ConfigureApplicationIdentity(IServiceCollection services)
{
            services.AddScoped<IPasswordHasher<ApplicationUser>, Kentico.Membership.PasswordHasher<ApplicationUser>>();
            services.AddScoped<IMessageService, MessageService>();
            services.AddSingleton<ExternalRoles>();
            services.AddApplicationIdentity<ApplicationUser, ApplicationRole>(options =>
            {
                // A place to configure Identity options, such as password strength requirements
                options.SignIn.RequireConfirmedEmail = true;
            })
                    // Adds default token provider used to generate tokens for
                    // email confirmations, password resets, etc.
                    .AddApplicationDefaultTokenProviders()
                    // Adds an implementation of the UserStore for working
                    // with Xperience user objects
                    .AddUserStore<ApplicationUserStore<ApplicationUser>>()
                    // Adds an implementation of the RoleStore used for
                    // working with Xperience roles
                    .AddRoleStore<ApplicationRoleStore<ApplicationRole>>()
                    // Adds an implementation of the UserManager for Xperience membership
                    .AddUserManager<ApplicationUserManager<ApplicationUser>>()
                    // Adds the default implementation of the SignInManger
                    .AddSignInManager<SignInManager<ApplicationUser>>();
 
            //DocSection:ExternalAuth
            services.AddAuthentication()
               .AddOpenIdConnect(options =>
               {
                   options.ClientId = SettingsKeyInfoProvider.GetValue("ClientId", SiteContext.CurrentSiteName);
                   options.ClientSecret = SettingsKeyInfoProvider.GetValue("ClientSecret", SiteContext.CurrentSiteName);
                   options.CallbackPath = SettingsKeyInfoProvider.GetValue("CallbackPath", SiteContext.CurrentSiteName);
                   options.MetadataAddress = SettingsKeyInfoProvider.GetValue("B2CMetadataAddress", SiteContext.CurrentSiteName);
                   options.SignedOutCallbackPath = SettingsKeyInfoProvider.GetValue("SignedOutCallbackPath", SiteContext.CurrentSiteName);
               });
            //EndDocSection:ExternalAuth
 
            services.AddAuthorization();
            services.AddScoped<ApplicationRoleStore<ApplicationRole>>();
            services.ConfigureApplicationCookie(c =>
            {
                c.LoginPath = new PathString("/");
                c.ExpireTimeSpan = TimeSpan.FromDays(14);
                c.SlidingExpiration = true;
                c.Cookie.Name = AUTHENTICATION_COOKIE_NAME;
            });
 
            // Registers the authentication cookie with the 'Essential' cookie level
            // Ensures that the cookie is preserved when changing a visitor's allowed cookie level below 'Visitor'
            CookieHelper.RegisterCookie(AUTHENTICATION_COOKIE_NAME, CookieLevel.Essential);
}


Note: Create above setting keys on CMS
 
Step 3: Create the following controller to handle the External Authentication


public class ExternalAuthenticationController : Microsoft.AspNetCore.Mvc.Controller
    {
        //DocSection:DependencyInjection
        private readonly SignInManager<ApplicationUser> signInManager;
        private readonly ApplicationUserManager<ApplicationUser> userManager;
        private readonly ApplicationRoleStore<ApplicationRole> roleStore;
        private readonly IEventLogService eventLogService;
        private readonly ISiteService siteService;
 
        /// <summary>
        /// ExternalAuthenticationController Constructor
        /// </summary>
        /// <param name="signInManager"></param>
        /// <param name="userManager"></param>
        /// <param name="roleStore"></param>
        /// <param name="eventLogService"></param>
        /// <param name="siteService"></param>
        public ExternalAuthenticationController(SignInManager<ApplicationUser> signInManager,
                                                ApplicationUserManager<ApplicationUser> userManager,
                                                ApplicationRoleStore<ApplicationRole> roleStore,
                                                IEventLogService eventLogService,
                                                ISiteService siteService)
        {
            this.signInManager = signInManager;
            this.userManager = userManager;
            this.roleStore = roleStore;
            this.eventLogService = eventLogService;
            this.siteService = siteService;
        }
        //EndDocSection:DependencyInjection
 
 
        //DocSection:ExternalAuth
        /// <summary>
        /// Redirects authentication requests to an external service.
        /// Posted parameters include the name of the requested authentication middleware instance and a return URL.
        /// </summary>
        [HttpPost]
        [AllowAnonymous]
        [ValidateAntiForgeryToken]
        public IActionResult RequestExternalSignIn(string provider, string returnUrl)
        {
            // Sets a return URL targeting an action that handles the response
            string redirectUrl = Url.Action(nameof(ExternalSignInCallback), new { ReturnUrl = returnUrl });
 
            // Configures the redirect URL and user identifier for the specified external authentication provider
            AuthenticationProperties authenticationProperties = signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl);
 
            // Challenges the specified authentiction provider, redirects to the specified 'redirectURL' when done
            return Challenge(authenticationProperties, provider);
        }
 
 
        /// <summary>
        /// Handles responses from external authentication services.
        /// </summary>
        [HttpGet]
        [AllowAnonymous]
        public async Task<IActionResult> ExternalSignInCallback(string returnUrl, string remoteError = null)
        {
            // If an error occurred on the side of the external provider, displays a view with the forwarded error message
            if (remoteError != null)
            {
                return RedirectToAction(nameof(ExternalAuthenticationFailure));
            }
 
            // Extracts login info out of the external identity provided by the service
            ExternalLoginInfo loginInfo = await signInManager.GetExternalLoginInfoAsync();
 
            // If the external authentication fails, displays a view with appropriate information
            if (loginInfo == null)
            {
                return RedirectToAction(nameof(ExternalAuthenticationFailure));
            }
 
            // Attempts to sign in the user using the external login info
            SignInResult result = await signInManager.ExternalLoginSignInAsync(loginInfo.LoginProvider, loginInfo.ProviderKey, isPersistent: false);
 
            // Success occurs if the user already exists in the connected database and has signed in using the given external service
            if (result.Succeeded)
            {
                eventLogService.LogInformation("External authentication", "EXTERNALAUTH", $"User logged in with {loginInfo.LoginProvider} provider.");
                return RedirectToLocal(returnUrl);
            }
 
            // The 'NotAllowed' status occurs if the user exists in the system, but is not enabled
            if (result.IsNotAllowed)
            {
                // Returns a view informing the user about the locked account
                return RedirectToAction(nameof(Lockout));
            }
            else
            {
                return View();
 
                // Optional extension:
                // Send the loginInfo data as a view model and create a form that allows adjustments of the user data.
                // Allows visitors to resolve errors such as conflicts with existing usernames in Xperience.
                // Then post the data to another action and attempt to create the user account again.
                // The action can access the original loginInfo using the AuthenticationManager.GetExternalLoginInfoAsync() method.
            }
        }
 
        /// <summary>
        /// Lockout
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [AllowAnonymous]
        public IActionResult Lockout()
        {
            return View();
        }
 
        /// <summary>
        /// ExternalAuthenticationFailure
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [AllowAnonymous]
        public IActionResult ExternalAuthenticationFailure()
        {
            return View();
        }
 
 
        /// <summary>
        /// Creates users in Xperience based on external login data.
        /// </summary>
        private async Task<IdentityResult> CreateExternalUser(ExternalLoginInfo loginInfo)
        {
 
           
            // Prepares a new user entity based on the external login data
            ApplicationUser user = new ApplicationUser
            {
                UserName = ValidationHelper.GetSafeUserName(loginInfo.Principal.FindFirstValue(ClaimTypes.Email) ??
                                                        loginInfo.Principal.FindFirstValue(ClaimTypes.Email),
                                                        siteService.CurrentSite.SiteName),
                Email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email),
                Enabled = true, // The user is enabled by default
                IsExternal = true // IsExternal must always be true for users created via external authentication
                // Set any other required user properties using the data available in loginInfo
            };
 
            // Attempts to create the user in the Xperience database
            IdentityResult result = await userManager.CreateAsync(user);
            if (result.Succeeded)
            {
                if (externalRoles.Length > 0) {
                    IdentityResult roleResult = await userManager.AddToRolesAsync(user, externalRoles);
                    if (roleResult.Succeeded)
                    {
                        eventLogService.LogEvent(EventTypeEnum.Information, "External Login", "UserRole Assign", "Role(s) Assigned to User");
                    }
                }
                    // If the user was created successfully, creates a mapping between the user and the given authentication provider
                    result = await userManager.AddLoginAsync(user, loginInfo);
            }
 
            return result;
        }
 
 
        /// <summary>
        /// Redirects to a specified return URL if it belongs to the MVC website or to the site's home page.
        /// </summary>
        private IActionResult RedirectToLocal(string returnUrl)
        {
            if (Url.IsLocalUrl(returnUrl))
            {
                return Redirect(returnUrl);
            }
 
            return RedirectToAction(nameof(Index), "Home");
        }
        //EndDocSection:ExternalAuth
    }


Step 4: Create the following partial view for displaying Login/Username links on page – and call this on Master Layout


@using System.Security.Principal
@using CMS.DataEngine;
@using CMS.SiteProvider;
@using Microsoft.Identity.Web;
@using Microsoft.Extensions.Options
@using Kentico.Membership
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Identity
 
 
@*DocSection:ExternalAuthView*@
 
 
@inject SignInManager<ApplicationUser> SignInManager
 
@{
    var currentPageUrl =  Context.Request.Path + Context.Request.QueryString;
    @* Gets a collection of the authentication services registered in your application's startup class *@
    var signInProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    if (signInProviders.Count == 0)
    {
    <div style="display:none">
                <p>
                    There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                    for details on setting up this ASP.NET application to support logging in via external services.
                </p>
    </div>
    }
    else
    {
    <li>
                @* Generates a form with buttons targeting the RequestSignIn action. Optionally pass a redirect URL as a parameter. *@
 
                @foreach (AuthenticationScheme provider in signInProviders)
            {
                    @if (User.Identity?.IsAuthenticated == false)
                {
                    var displayName = (provider.DisplayName == "OpenIdConnect") ? "SIGNIN" : provider.DisplayName;
                        <form asp-controller="ExternalAuthentication" asp-action="RequestExternalSignIn" asp-route-returnurl="@currentPageUrl" method="post" class="">
 
                            <button type="submit" class="nav-link text-dark" id="ssoLoginBtn" name="provider" style="color:white;display:none;" value="@provider.Name">
                                @displayName
                            </button>
                        </form>
                }
            }
    </li>
    @if (User.Identity?.IsAuthenticated == true)
        {
            <li >
                <a class="navbar-text text-dark">@User.Identity?.Name</a>
            </li>
        }
    }
}
@*EndDocSection:ExternalAuthView*@
 
To include the necessary functionality in your website, add the following code to your Layout.cshtml file: <partial name="_LoginPartial" />.
 
After completing the integration steps, proceed to build and run your website to observe the desired output.
 

- My ASP.NET Application
We use cookies to provide the best possible browsing experience to you. By continuing to use our website, you agree to our Cookie Policy