For the second consecutive year, Blend has been named to the Inc Best Workplaces list! Read the full press release here.

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.

Input Sanitizing and Common Pitfalls of User Input

Joe Kepley

With the rise of malicious bots on the Internet, websites are becoming more vulnerable to data breaches. Implementing sanitizing techniques can protect your website and your users.

July 9, 2024

Optimizely Release Notes — March-April 2024

Bob Davidson

The following release notes highlight major or interesting changes in Optimizely's products from March and April 2024.

May 28, 2024

Optimizely Release Notes — January-February 2024

Bob Davidson

The following release notes highlight major or interesting changes in Optimizely's products from January and February 2024.

March 15, 2024

Introducing Blend's Little Import Tool

Bob Davidson

Content migration is difficult — really difficult. Which is why Blend has taken our decades of migration experience and developed Blend's Little Import Tool — a utility for improving the import process

March 8, 2024

Check out our most recent articles on development.