Integrating Azure AD with Optimizely CMS 12

The introduction of Optimizely CMS 12 brings many changes, chief among them being the switch from full .NET framework to .NET 5 / Core. One area that has seen some relatively large changes is integrating with Azure Active Directory. Blend's Director of Development Bob Davidson talks through what this means.


Authored by


  • Optimizely
  • Development

The introduction of Optimizely CMS 12 brings many changes, chief among them being the switch from full .NET framework to .NET 5 / Core. 

With the change of frameworks comes many subtle and not-so-subtle changes to many aspects of the site setup and development. One area that has seen some relatively large changes is integrating with Azure Active Directory. The official Episerver documentation gives some good guidance here, but (at least at the time of this writing) leaves out a few details. So here I’ll walk through the process, start to finish, for switching your Optimizely CMS 12 site from built-in authentication to Azure Active Directory authentication.

The process consists of two basic phases: Setting up the Azure AD app, which is mostly clicking, and configuring the CMS, which is mostly coding.

Set up the Azure app.

In order to integrate with Azure AD, you first need to set up the application. These steps will walk you through a basic Azure AD Application setup.

  1. Log in to Azure and go to your Azure Active Directory.
  2. Select App Registrations in the sidebar, and create a new App Registration.

  3. Give it a sensible name. Unless you have a good reason not to, you want to select “Accounts in this organizational directory only”
  4. Optionally, you can enter the redirect URI you’ll be using for local development. For example: https://localhost:44307/signin-oidc. This is basically the IIS Express domain and port, followed by a signin-oidc path segment.
  5. Click Register

  6. Make note of the Application (Client) ID and Directory (tenant) ID. You’ll need these later.

  7. Select Authentication in the sidebar.
  8. Add any additional domains you’ll be logging into this application from. For example: https://optimizely-cms-website.local/signin-oidc.
  9. Make sure “ID tokens” under the “Implicit grant and hybrid flows” heading is checked.

  10. Next select App Roles from the sidebar.
  11. Add a “WebAdmins” and “WebEditors” role. These are the default roles Optimizely CMS will look for out of the box.
  12. You’ll want to allow Users/Groups as member types
  13. Make sure “Do you want to enable this app role?” is checked.


Add users to your app.

Now that the application is set up, you’ll need to add the users who should have access to your site to your application.

  1. Head back to your Azure Active Directory, and select Enterprise applications.
  2. You should see a new Enterprise Application with the same name as your App Registration. Select it.
  3. Select Users and groups from the sidebar
  4. Click the Add user/group button.
  5. Select the user(s) you want to assign to this application
  6. Select the role you want to assign the user(s) you’ve selected to.
  7. Click Assign.

Install the Microsoft Open ID package.

Now that the Azure AD application is setup, it’s time to configure the CMS to use it.

Optimizely’s documentation mentions that you’ll need to uninstall the Episerver.CMS package. The problem, of course, is that Episerver.CMS is the package that installs the entire Optimizely CMS. In fact, in a new install, it’s the only directly installed package.

So what to do? Well, the Episerver.CMS package is sort of a meta-package. It holds very little code itself, but instead is mostly a collection of nuget dependencies. So the work-around I’ve applied is to remove the Episerver.CMS package and install all of its dependencies, except for the EPiServer.Cms.UI.AspNetIdentity package, which the documentation says will be incompatible.

Currently, that means installing these packages for the CMS:

  • EPiServer.Hosting
  • EPiServer.CMS.AspNetCore.HtmlHelpers
  • EPiServer.CMS.UI
  • EPiServer.CMS.UI.VisitorGroups
  • EPiServer.CMS.TinyMce

And this package for the AD integration:

  • Microsoft.AspNetCore.Authentication.OpenIdConnect

It may be worth checking the most recent Episerver.CMS package’s dependencies to see if this list of CMS dependencies is still accurate.

Configure startup.

You’ll want to start by adding your Client and Tenant IDs to your configuration. The documentation hard-codes these values, but I prefer to put them in configuration files.

  1. Add the settings to your appsettings.json. Note: the structure here is arbitrary, you can use whatever keys or structure you prefer.

      "Logging": {
        "LogLevel": {
          "Default": "Warning",
          "Microsoft": "Warning",
          "EPiServer": "Warning",
          "Microsoft.Hosting.Lifetime": "Warning"
      "urls": "http://*:8000/;https://*:8001/;",
      "AllowedHosts": "*",
      "Authentication": {
        "AzureClientID": "56e0ddbc-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "AzureTenantID": ""
      "ConnectionStrings": {
        "EPiServerDB": "..."

    The Application/Client ID can remain as-is. For the Directory/Tenant ID, I convert it here to the URI format, basically:{{TENANT ID}}/v2.0.

  2. Next, to access the configuration, you’ll need to inject IConfiguration to your Startup class constructor.

    public class Startup
        private readonly IWebHostEnvironment _webHostingEnvironment;
        private readonly IConfiguration _configuration;
        public Startup(IWebHostEnvironment webHostingEnvironment, IConfiguration configuration)
            _webHostingEnvironment = webHostingEnvironment;
            _configuration = configuration;


  3. Now that you’ve removed the EPiServer.CMS package, the AddCms() extension method is gone. Luckily, that function is relatively simple and can be replaced with the following code. It’s important to note here that any changes to the AddCms() method in the future will not appear here. That will be something to keep an eye on.

    // Replace services.AddCms() with equivalent code


  4. Finally, add the authentication code from Optimizely’s documentation, with a few small tweaks for configuration.

    services.AddAuthentication(options =>
            options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
        .AddOpenIdConnect(options =>
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            options.ClientId = _configuration["Authentication:AzureClientID"];
            options.Authority = _configuration["Authentication:AzureTenantID"];
            options.CallbackPath = "/signin-oidc";
            options.TokenValidationParameters = new TokenValidationParameters
                ValidateIssuer = false,
                RoleClaimType = ClaimTypes.Role,
                NameClaimType = ClaimTypes.Email
            options.Events.OnAuthenticationFailed = context =>
                return Task.FromResult(0);
            options.Events.OnTokenValidated = (ctx) =>
                var redirectUri = new Uri(ctx.Properties.RedirectUri, UriKind.RelativeOrAbsolute);
                if (redirectUri.IsAbsoluteUri)
                    ctx.Properties.RedirectUri = redirectUri.PathAndQuery;
                //Sync user and the roles to EPiServer in the background
                ServiceLocator.Current.GetInstance<ISynchronizingUserService>().SynchronizeAsync(ctx.Principal.Identity as ClaimsIdentity);
                return Task.FromResult(0);

In my setup, I had to make one final tweak, which may be related to how our applications are set up in our Azure environment. I found that the email claim was not coming through, however, a preferred_username claim was, so I switched the token validation parameters to use that claim instead.

options.TokenValidationParameters = new TokenValidationParameters
    ValidateIssuer = false,
    RoleClaimType = ClaimTypes.Role,
    NameClaimType = "preferred_username"

With all this done, you should now be able to navigate to /Episerver/CMS of your local install and be redirected to your Azure Active Directory application for authentication.

We're hiring! 

We're hiring a Senior .NET Developer. We are on the lookout for a talented, detail-oriented, experienced .NET stack developer to join our expanding development team.

Resources on .NET development.

We’ve written at length, both here and beyond, on .NET development.

The Web Project Guide joins Content Strategy Insights podcast Off-site link

The Web Project Guide’s Corey Vilhauer and Deane Barker joined Larry Swanson on the Content Strategy Insights podcast to talk about all things The Web Project Guide — how the book came to be, how it ties to the work of building websites, and what's next as the book expands into the podcast space.

November 30, 2022 | The Web Project Guide

How Code Rots (and What To Do About It)

Just like humans, animals, and opinions, websites age. This means — again, just like humans, animals, and opinions — websites need to be cared for. Here are a few thoughts on how to manage and prevent "code rot."

October 28, 2022

2022 OMVP Summit Review

Bob Davidson

The Optimizely Most Valuable Professional (OMVP) program is designed to promote collaboration and expertise among Optimizely developers. This year was Bob's first year as an OMVP — and these are his thoughts.

October 21, 2022

The Web Project Guide on Agile Digital Transformation Off-site link

The Web Project Guide’s Corey Vilhauer and Deane Barker joined Tim Butara and the Agile Digital Transformation podcast to talk about the process of writing the book, the web project process itself, and the role of agility when moving toward a successful launch.

September 29, 2022 | The Web Project Guide

Check out our most recent articles on development.