- How to Create a Widget Designer using Thunder in Sitefinity
- How to test ACTITO APIs from PostMan
- How to integrate ACTITO API into Kentico 13 Xperience 13 Core MVC website
- Integrate Kentico Xperiecne 13 Core MVC with SSO using Azure AD B2C
- Create Blogs and Posts (Comments) in Sitefinity
- Kentico Hierarchical Transformation Layout
- How to assign global permissions to a user or role using permissions service in Sitefinity
- How to Create Custom widget in Sitefinity
- How to Create Custom widget using MVC Feather in Sitefinity
- How to Create Forms in Sitefinity
- How to Create Product types in E-commerce and its fields in Sitefinity
- How to Create Search index in Sitefinity
- How to Create Template and how to use it in Sitefinity
- How to implement Dynamic data retrieving in Sitefinity
- How to use Module Builder in Sitefinity
- IIS express configuration to host websites
- Unigrid
- UniPager
- UniSelector
- Making Responsive compatible on IE7/IE8 using response.js
- Client Side Compatibility issues on Macintosh
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
To include the necessary functionality in your website, add the following code to your Layout.cshtml file: <partial name="_LoginPartial" />.
@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*@
After completing the integration steps, proceed to build and run your website to observe the desired output.