Configure Umbraco to use Azure AD for Back Office user authentication

Authentication In Umbraco

Umbraco CMS (v7.9.2) features a well known and standard username/password scenario for handling authentication. This can be fine but corporate Umbraco adapters would probably not be to happy when we ask them to remember one more password as well as giving up centralized user account management.

Corporations who’s IT is Microsoft based will often have security matters handled by Active Directory – and if taking IT serious these days –  have Federation Services configured and synchronization with Azure Active Directory in place.

I wrote a post regarding the business value and importance of SSO when using Umbraco in a Microsoft infrastructure based on Azure here.

When Umbraco CMS is added to the Corporate Azure mix – corporations will require or rather should require that matters of Authentication should be handled by Azure Active Directory.

This post  is a tutorial on how to make this setup work in practice. Its easy ones you invested  hours on making all the pieces fit – and here is what I have learned, so that you don’t have to.

No plug and play solution found

I was unable to find a “plug and play” solution that enables Azure AD authentication for Umbraco Back Office Users. I might be wrong – so please send me a comment if that is the case.

Existing official Solution

There is some documentation to be found in the configuration files when  Umbraco is installed and the  Umbraco’s community site does describe necessary steps needed to enable external login providers. They eaven provide a tutorial on how to enable Google Authentication in Umbraco.

There is a  2 year old (from 2015) NuGet package available named UmbracoCms.IdentityExtensions.AzureActiveDirectory. It basically generates some C# files in you project – helping you configuring Umbraco to use Azure AD authentication. (source code on github).  by configuring OWIN.

The problem was that I could not get it to work out of the box. I really do not know why – but browsing Azures AD documentation and tutorials – I discovered the generated C# files added to my project were missing some settings.

We are not going to use the above mentioned NuGetPackage.

Install NuGet Packages

We need to install two NuGet Packages to enable OpenId to your project:

Using version 3.1.0 of all of the above keeps you safe 🙂

Allot of dependencies will be installed, so be warned. Here is a I got when installing UmbracoCms.IdentityExtensions on Umbraco Cloud 7.10.4:

Open Web Interface for .NET

Umbraco embraces Open Web Interface for .NET (OWIN) being the standard method of handling authentication in ASP.NET.  To get started you’ll need to create an App Registration using the Azure Portal.  If you are unfamiliar to the process there is exellent guide here.

When the app has been registered, you will need to write down and understand a couple of values:

ClientId / ApplicationId

The ClientId (ApplicationId on the Azure Portal) uniquely identifies the app towards Azure AD.

AADInstance

Azure Active Directory Instance is always: “https://login.microsoftonline.com/” and identifies “public Azure” – this value might be something else if you are on a private cloud, but I have never seen such a private cloud in action.

Tenant ID

Tennant id is a Guid value uniquely identifying your Azure tenant.

Adding code

The App Settings section in Umbraco’s web.config should include the above mentioned values from Azure’s App Registration:

<add key="ida:ClientId" value="" />
<add key="ida:AADInstance" value="https://login.microsoftonline.com/" />
<add key="ida:TenantId" value="" />
<add key="ida:PostLoginUrl" value="https://localhost/umbraco" />

Also add a class named UmbracoAzureActiveDirectoryExtensions to serve as extension method for the IAppBuilder we later will call during startup.

using System.Configuration;
using System.Security.Claims;
using System.Threading.Tasks;
using Owin;
using Microsoft.Owin.Security.OpenIdConnect;
using Microsoft.IdentityModel.Protocols;
using Umbraco.Web.Security.Identity;
using Umbraco.Core;

namespace AzureAdTestWebApp.App_Startup
{
    
    /// 


<summary>
    /// Extention methods for <see cref="IAppBuilder"/> enabling various 
    /// Azure Active Directory Authentication senarios in Umbraco CMS.
    /// </summary>



    /// <remarks>
    /// Uses OWIN 3 and v1 of Azure Active Directory Endpoints. 
    /// </remarks>
    public static class UmbracoAzureActiveDirectoryExtensions
    {
        // Client id also called "ApplicationId" in the Azure portal and is used to uniquely identify itself to Azure AD.
        private static readonly string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];

        // The Active Directory Instance always beeing: https://login.microsoftonline.com/ for public Azure. 
        // (Might be something elese with private Azure)
        // ReSharper disable once InconsistentNaming
        private static readonly string AADInstance = ConfigurationManager.AppSettings["ida:AADInstance"];

        // The "primary" key of you Azure tenant. (a Guid)
        private static readonly string TenantId = ConfigurationManager.AppSettings["ida:TenantId"];

        // The Authority is the sign-in URL of the tenant. it is t STS + your tenantid
        private static readonly string Authority = AADInstance + TenantId;

        // After login - where should you be redirected?
        private static readonly string LoginUrl = ConfigurationManager.AppSettings["ida:PostLoginUrl"];
        
        /// 


<summary>
        /// Configures Umbraco to use Azure Active Directory and OpenIdConnect to authenticate backend users. AutoLinking
        /// is enabled to automatically create a user in Umbraco - and linking that user to the Azure AD user.
        /// </summary>



        /// <remarks>
        /// Uses the <see cref="ClaimTypes.Upn"/> as unique identifier (the email address) and adds the 
        /// Emailaddress Claim to get AutoLinking working
        /// </remarks>
        /// <param name="app">The IAppBuilder <see cref="IAppBuilder"/> to configure</param>
        /// <param name="caption">The text displaied on the login-screen if the user is not authorized</param>
        /// <param name="style">css class defining the login-button</param>
        /// <param name="icon">css icon defining the icon to use</param>
        /// <example>
        /// <code>app.UseAzureActiveDirectoryForBackOffice();</code>
        /// </example>
        public static void UseAzureActiveDirectoryForBackOffice(this IAppBuilder app, string caption = "Windows", string style = "btn-windows", string icon = "fa-windows")
        {
            var adOptions = new OpenIdConnectAuthenticationOptions
            {
                ClientId = ClientId,
                Authority = Authority,
                RedirectUri = LoginUrl,
                PostLogoutRedirectUri = LoginUrl,

                // Scope is the requested scope: OpenIdConnectScopes.OpenIdProfileis 
                // equivalent to the string 'openid profile': in the consent screen, 
                // this will result in 'Sign you in and read your profile'
                Scope = OpenIdConnectScopes.OpenIdProfile,

                // ResponseType is set to request the id_token - which contains basic 
                // information about the signed-in user
                ResponseType = OpenIdConnectResponseTypes.IdToken,

                // ValidateIssuer set to false to allow work accounts from any organization 
                // to sign in to your application
                // To only allow users from a single organizations, set ValidateIssuer to 
                // true and 'tenant' setting in web.config to the tenant name or Id (example: contoso.onmicrosoft.com)
                // To allow users from only a list of specific organizations, set ValidateIssuer to true and use ValidIssuers parameter
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters() { ValidateIssuer = false },

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    // Because umbraco does not use the same Claims as Azure AD - We need to find the identities email
                    // and add the Email claim to the Identity so other areas function.
                    SecurityTokenValidated = notification =>
                    {
                        // Find the claim "upn" that uniquely defines a user on Azure AD - whos value always will be the users email address.
                        var upnClaim = notification.AuthenticationTicket.Identity.FindFirst(ClaimTypes.Upn);

                        // If you have no upn ... it's not from Azure AD, so do nothing.
                        if (upnClaim == null) return Task.FromResult(0);

                        // Add the email address to the Email Claim of the identity, so that Umbraco's AutoLink 
                        // feature works properly.
                        notification.AuthenticationTicket.Identity.AddClaim(new Claim(ClaimTypes.Email, upnClaim.Value));
                        return Task.FromResult(0);
                    }
                },
                // A bit unclear - but it works with Umbraco. Got this setting from: https://github.com/AzureADSamples/WebApp-WebAPI-OpenIDConnect-DotNet
                SignInAsAuthenticationType = Constants.Security.BackOfficeExternalAuthenticationType
            };
            // Settup the loginscreen to use Azure Active Directory 
            adOptions.ForUmbracoBackOffice(style, icon);

            // Text shown on loginscreen.
            adOptions.Caption = caption;

            //Need to set the auth type as the issuer path
            adOptions.AuthenticationType = $"https://sts.windows.net/{TenantId}/";

            //This will auto-create users based on the authenticated user if they are new
            //NOTE: This needs to be set after the explicit auth type is set
            var autoLinkOptions =
                new ExternalSignInAutoLinkOptions(true, defaultUserGroups: null)
                {
                    OnAutoLinking = (backOfficeIdentityUser, externalLoginInfo) =>
                    {
                        //this callback will execute when the user is being auto-linked but before it is created
                        //so you can modify the user before it's persisted
                    }
                };

            // AutoLink setup
            adOptions.SetExternalSignInAutoLinkOptions(autoLinkOptions);
           
            // Finally, use OpenIdConnect with the options given above. 
            app.UseOpenIdConnectAuthentication(adOptions);
        }
    }
}

To bind it all together – create a class “UmbracoCustomOwinStartup”:

using AzureAdTestWebApp.App_Startup;
using Owin;
using Microsoft.Owin;
using Umbraco.Core;
using Umbraco.Core.Security;
using Umbraco.Web.Security.Identity;

// To use this call when loading OWIN change the appSetting 
// "owin:appStartup" to be "UmbracoCustomOwinStartup"
[assembly: OwinStartup("UmbracoCustomOwinStartup", typeof(UmbracoCustomOwinStartup))]
namespace AzureAdTestWebApp.App_Startup
{  
    public class UmbracoCustomOwinStartup
    {
        /// 


<summary>
        /// Configure Umbraco CMS to use Azure Active Directiry for Authentication of Back Office users. 
        /// </summary>



        /// <remarks>
        /// </remarks>
        public void Configuration(IAppBuilder app)
        {
            app.SanitizeThreadCulture();
            app.SetUmbracoLoggerFactory();

            // Configure the Identity user manager for use with Umbraco Back office
            // *** EXPERT: There are several overloads of this method that allow you to specify a custom UserStore or even a custom UserManager!            
            app.ConfigureUserManagerForUmbracoBackOffice(
                ApplicationContext.Current,
                // The Umbraco membership provider needs to be specified in order to maintain backwards compatibility with the 
                // user password formats. The membership provider is not used for authentication, if you require custom logic
                // to validate the username/password against an external data source you can create create a custom UserManager
                // and override CheckPasswordAsync
                MembershipProviderExtensions.GetUsersMembershipProvider().AsUmbracoMembershipProvider());

            // Ensure owin is configured for Umbraco back office authentication
            app.UseUmbracoBackOfficeCookieAuthentication(ApplicationContext.Current, PipelineStage.Authenticate)
                .UseUmbracoBackOfficeExternalCookieAuthentication(ApplicationContext.Current,
                    PipelineStage.Authenticate)
                .UseUmbracoPreviewAuthentication(ApplicationContext.Current, PipelineStage.Authorize);

            // Configure the backoffice to use Azure ActiveDirectory
            app.UseAzureActiveDirectoryForBackOffice();

            // Got this from decompiling the standard owin startup classes parent
            app.UseSignalR();

            app.FinalizeMiddlewareConfiguration();
        }
    }
}

Next we will instruct Umbraco to use the newly created Configuration by updating the owin:appStartup in web.config‘s appsettings from UbracoDefaultOwinStartup to the type we created for this purpose:

<add key="owin:appStartup" value="UmbracoCustomOwinStartup"></add>

When you start your Umbraco Backoffice your loginscreen should look something like this:
Umbraco CMS Login Screen Azure Active Directory

Giving you the option to login using your Windows Account or Corporate account as long as it somehow is handled by Azure AD.

Leave a Reply

Your email address will not be published. Required fields are marked *