---
title: Integrate Entra ID using OpenID Connect
slug: cms-12/docs/integrate-azure-ad-using-openid-connect
createdAt: 2026-08-10T15:55:55.000Z
---

:::hint{type="info"}
**Note**
If you have `EPiServer.CMS.UI.AspNetIdentity` installed, make sure you are not calling `services.AddCmsAspNetIdentity()` in `startup.cs`. Otherwise, the user interface may not look for synchronized users when setting access rights.
:::

## About Entra ID and OpenID

[Entra ID (formerly Azure AD)](https://docs.microsoft.com/en-us/azure/active-directory/) is Microsoft's multi-tenant cloud-based directory and identity management service. Entra ID provides single sign-on (SSO) access to many cloud-based SaaS applications and includes a full suite of identity management capabilities.

[OAuth](https://oauth.net/) is an open standard for authorization also used by Entra ID. [OpenID Connect](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-openid-connect-code) is built on top of OAuth and extends this so you can use it as an authentication protocol rather than just an authorization protocol.

For information about how the protocols work, see [Authentication Scenarios for Entra ID](https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-vs-authorization) and [Secure your application by using OpenID Connect and Entra ID](https://docs.microsoft.com/en-us/learn/modules/secure-app-with-oidc-and-azure-ad/).

## Prerequisites

You can replace virtual roles with roles defined in the manifest to delegate this control from the application to Azure. See Adding application roles in Entra ID.

### Install NuGet packages

Open Package Manager in Visual Studio and install the following package:

```javascript
Install-Package Microsoft.AspNetCore.Authentication.OpenIdConnect
```

### Configure OpenID Connect

To configure the OpenID Connect, add the following code in the `startup` class. The `OnSignedIn` event is used to sync the user and group membership to Optimizely. You can also use this event for custom logic (for example, adding custom data to the user profile).

```csharp
public void ConfigureServices(IServiceCollection services) {
  ...
  services
  .AddAuthentication(options => {
    options.DefaultAuthenticateScheme = "azure-cookie";
    options.DefaultChallengeScheme = "azure";
  })
  .AddCookie("azure-cookie", options => {
    options.Events.OnSignedIn = async ctx => {
      if (ctx.Principal?.Identity is ClaimsIdentity claimsIdentity) {
        // Syncs user and roles so they are available to the CMS
        var synchronizingUserService = ctx
          .HttpContext
          .RequestServices
          .GetRequiredService<ISynchronizingUserService>();

        await synchronizingUserService.SynchronizeAsync(claimsIdentity);
      }
    };
  })
  .AddOpenIdConnect("azure", options => {
    options.SignInScheme = "azure-cookie";
    options.SignOutScheme = "azure-cookie";
    options.ResponseType = OpenIdConnectResponseType.Code;
    options.CallbackPath = "/signin-oidc";
    options.ClientSecret = "CLIENT SECRET";
    options.UsePkce = true;

    // If Entra ID is register for multi-tenant
    //options.Authority = "https://login.microsoftonline.com/" + "common" + "/v2.0";
    options.Authority = "https://login.microsoftonline.com/" + "TENANT ID" + "/v2.0";
    options.ClientId = "CLIENT ID";

    options.Scope.Clear();
    options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
    options.Scope.Add(OpenIdConnectScope.OfflineAccess);
    options.Scope.Add(OpenIdConnectScope.Email);
    options.MapInboundClaims = false;

    options.TokenValidationParameters = new TokenValidationParameters {
      	RoleClaimType = "roles",
        NameClaimType = "preferred_username",
        ValidateIssuer = true
    };

    options.Events.OnRedirectToIdentityProvider = ctx => {
      // Prevent redirects from being cached.
			ctx.Response.Headers.CacheControl = "no-cache,no-store";

      // Prevent redirect loop
      if (ctx.Response.StatusCode == 401) {
        ctx.HandleResponse();
      }

      return Task.CompletedTask;
    };

    options.Events.OnAuthenticationFailed = context => {
      context.HandleResponse();
      context.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(context.Exception.Message));
      return Task.CompletedTask;
    };
  });
  ...
}
```

:::hint{type="info"}
**Note**
Replace the following items in the previous code for authentication and identification:

- **Client ID** – Issued by Optimizely, this is a public identifier for the application (client) that requests authentication or access to resources.
- **Client Secret** – Issued by Optimizely, this is a confidential value used to authenticate the application to the identity provider. It acts like a password for the application and should be kept secure. The client secret is used in conjunction with the client ID to prove the application's identity when requesting tokens from Optimizely.
- **Tenant ID** – The tenant ID is a unique identifier for a specific tenant within a multi-tenant environment, such as Azure Active Directory. It specifies the directory or organization in which the application is registered and from which it requests authentication.

Together, these components help ensure that only authorized applications can access resources and that the authentication process is securely managed.
:::

## Add application roles in Entra ID

You must declare application roles in the active directory application, such as **WebEditors** and **WebAdmins**. The application owner (developer of the app) or the global administrator of the developer's directory can declare roles for an application.

1. Go to the **Active Directory** node and the **Applications** tab in the Azure Management Portal.
2. Click to open the application for which you want to declare application roles.
3. Click **App roles**.
4. Create **WebAdmins**, **WebEditors**, and **Administrators** app roles: This is an example of app roles that declare WebAdmins and WebEditors. You can modify it according to your application roles.

## Assign users and groups to application roles

When a global administrator of the customer's organization has installed your application, they (or a user accounts administrator) can assign users and groups to your application:

1. Go to the **Users** tab under the application you want to assign users and groups.
2. Select a user and click **Assign** on the bottom bar to assign the desired role to the user.

## Address CDN caching issues

Implement `OnRedirectToIdentityProvider` to address CDN caching issues. Setting the `Cache-Control` and `CDN-Cache-Control` headers to `"no-store, no-transform, no-cache"` is a good practice for security and data freshness, especially in authentication-related scenarios like redirecting to an identity provider. Also, consider the performance implications because avoiding caching might increase the load on your servers.

```csharp
options.Events.OnRedirectToIdentityProvider = ctx => {
  ctx.Response.Headers["Cache-Control"] = "no-store, no-transform, no-cache";
  ctx.Response.Headers["CDN-Cache-Control"] = "no-store, no-transform, no-cache";

 return Task.CompletedTask;
}
```
