Blend Interactive proud to be included on Inc. Magazine's Best Workplace for 2023 list.

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 always on the lookout for talent! 

We are not currently hiring a Senior .NET Developer. However, we'd still love to get to know you. If you are a talented, detail-oriented, experienced .NET stack developer we are open to chatting about joining our development team.

Resources on .NET development.

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

Optimizely Release Notes — September-October 2023

The following release notes highlight major or interesting changes in Optimizely's products from September and October 2023.

November 13, 2023

Episode 24: Maintain and Improve (w/ David Hobbs) Off-site link

Corey and Deane discuss the people and rules that help run a website after launch. Then, David Hobbs, author of Website Product Management: Keeping Focused During Change, joins to talk about transferring a site from a project to a product — what that means to keep the site going after launch, where it most often fails, and how to streamline requests and set reasonable expectations for the future of the site.

October 17, 2023 | The Web Project Guide Podcast

Optimizely Release Notes — July-August 2023

The following release notes highlight major or interesting changes in Optimizely's products from July and August 2023.

September 15, 2023

Episode 22: Test and Launch the Site (w/ Bob Davidson) Off-site link

Corey and Deane talk about the concept of the “Nails List.” Then, Bob Davidson, Director of Development at Blend Interactive, joins to talk about how to get your site ready for launch, what makes a good QA practitioner, the role of quality assurance and testing in the development process, and how to prep the site so it doesn’t fall over when exposed to the real world. We also spend a lot of time talking up Jenna Bonn, Blend’s QA Practice Manager.

August 16, 2023 | The Web Project Guide Podcast

Check out our most recent articles on development.