Skip to main content

Outline

At a glance:
  • Authentication: Establishes who the user is (ASP.NET Core schemes, cookies, OIDC/SSO)
  • Authorization: Decides what the user can do (roles, permissions-to-functions, policies)
  • Access management: CMS-specific controls (ACLs, virtual roles, function permissions)
  • Hardening: Reduce UI attack surface (decoupled setup, path changes, policy gates, HTTPS/cookies)

Introduction

Security and access management in Optimizely CMS 12 (PaaS) are core platform concerns rather than optional add-ons. CMS 12 builds on ASP.NET Core security foundations and extends them with CMS-specific concepts such as content access control lists (ACLs), function-level permissions, and virtual roles.

This article introduces the security model for developers new to Optimizely CMS 12. It covers the mechanisms that control who can sign in, what they can do, and how access is enforced across the editorial UI, admin features, and custom code.

Security Architecture Overview

Optimizely CMS 12 security can be understood as three cooperating layers:

  • Authentication: Establishes the user identity (principal) used by the application.
  • Authorization: Evaluates whether that identity can access a given capability.
  • Access management: CMS-specific constructs - roles, permissions-to-functions, virtual roles, and ACLs - used to implement authorization rules.

In a PaaS deployment, the same model applies, but teams often integrate with enterprise identity providers (SSO) and adopt deployment-specific hardening measures such as cookie policy and editor/admin UI isolation.

Authentication in CMS 12

Authentication answers: "Who is the current user?" In CMS 12, it is configured using standard ASP.NET Core authentication middleware. Multiple schemes can be composed when the solution needs different authentication flows for different audiences - for example, editors versus site visitors.

Mixed-mode Authentication

Select a pattern to expand and see the code example.

Registering multiple cookie schemes

Register multiple authentication schemes and set the default explicitly when different site audiences require different authentication flows.

C#
services .AddAuthentication() .AddCookie("a-scheme") .AddCookie("another-scheme");
C#
services .AddAuthentication(options => { options.DefaultScheme = "another-scheme"; options.DefaultAuthenticateScheme = "another-scheme"; });
Adding OpenID Connect wired to a cookie scheme

Wire OIDC to a specific cookie scheme so sign-in and sign-out use the same scheme, then sign out of that scheme explicitly from a controller action.

C#
services .AddAuthentication() .AddCookie("a-scheme") .AddCookie("another-scheme") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "another-scheme"; options.SignOutScheme = "another-scheme"; // ... });
C#
public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync("another-scheme"); return View(); }

Integrating Entra ID (OpenID Connect)

In enterprise environments, CMS editor authentication is commonly delegated to an external identity provider. Optimizely supports integrating Entra ID (formerly Azure AD) via OpenID Connect using standard ASP.NET middleware.

  • If EPiServer.CMS.UI.AspNetIdentity is installed, avoid calling services.AddCmsAspNetIdentity() in Startup.cs, otherwise the UI may not look for synchronized users when setting access rights.

The sample configuration below includes a cookie OnSignedIn event that synchronizes the user and group membership into Optimizely. This synchronization step is central to CMS authorization because access rules are applied using users and roles as known by Optimizely.

C#
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; // ... });

Authorization Model

Once a user is authenticated, CMS authorization determines which protected resources and actions they can access. CMS 12 combines three mechanisms:

  • Role checks - including virtual roles evaluated at runtime
  • Permissions-to-functions - feature-level access control
  • Content ACLs - per-content access rights managed in the CMS

The practical implication for developers is that there are multiple "gates" to consider when implementing any protected resource:

  • Feature gates - for admin features, APIs, or diagnostics
  • UI gates - edit/admin UI access
  • Content gates - read/edit/publish rights on a specific content item

Permissions to Functions

Optimizely CMS includes a built-in system for assigning permissions to individual functions, managed under Config > Permissions to functions in the admin UI.

Permissions to Functions Patterns

Select a pattern to expand and see the code example.

Checking permissions programmatically

Two supported approaches to query whether the current user holds a given permission:

C#
// Alt 1 - via PermissionService bool hasPermission = ServiceLocator.Current .GetInstance<PermissionService>() .IsPermitted(HttpContext.Current.User, SystemPermissions.DetailedErrorMessage); // Alt 2 - via PrincipalInfo bool hasPermission = PrincipalInfo.Current .IsPermitted(SystemPermissions.DetailedErrorMessage);
Defining custom permissions in code

Annotate a class with [PermissionTypes] so permissions are picked up automatically and surfaced in the admin UI under Config > Permissions to functions.

C#
[PermissionTypes] public static class MyCustomPermissions { public const string GroupName = "MyCustomPermissions"; static MyCustomPermissions() { EditSettings = new PermissionType(GroupName, "EditSettings"); ViewSettings = new PermissionType(GroupName, "ViewSettings"); } public static PermissionType EditSettings { get; private set; } public static PermissionType ViewSettings { get; private set; } }
Protecting a controller with a permission

Apply [AuthorizePermission] to an MVC controller to gate access using a defined permission rather than a role.

C#
[AuthorizePermission("MyCustomPermissions", "EditSettings")] public class EditSettingsController : Controller { public ActionResult Index() { return View(); } }
Exposing a permission as a virtual role

When a downstream system can validate roles but not permissions, register a virtual role backed by a permission. This pattern is useful when integrating CMS security with external infrastructure that only understands roles.

C#
[InitializableModule] [ModuleDependency(typeof(EPiServer.Web.InitializationModule))] public class VirtualRoleInitializer : IInitializableModule { public void Initialize(InitializationEngine context) { var virtualRoleRepository = context.Locate.Advanced .GetInstance<IVirtualRoleRepository>(); virtualRoleRepository.Register("EditSettingsVirtualRole", new PermissionRole { Permission = MyCustomPermissions.EditSettings }); } public void Uninitialize(InitializationEngine context) {} }

Virtual Roles

Virtual roles are an extension of the role concept where membership is determined at runtime rather than stored in the database. CMS ships with several predefined virtual roles: Anonymous, Everyone, Authenticated, Creator, CmsAdmins, CmsEditors, SearchAdmins, and SearchEditors.

Virtual Role Configuration

Select a pattern to expand and see the code example.

Mapped roles in appsettings.json

Map one virtual role to one or more underlying identity-provider roles in configuration. Setting ShouldMatchAll: false means membership in any of the listed roles satisfies the virtual role.

JSON
"EpiServer": { "Cms": { "MappedRoles": { "Items": { "SearchAdmins": { "MappedRoles": [ "WebAdmins", "Administrators" ], "ShouldMatchAll": "false" } } } } }
Registering virtual roles programmatically

Register a custom virtual role type via DI in ConfigureServices.

C#
services.AddVirtualRole<MyVirtualRoleType>("MyVirtualRoleType");

Note: If SupportsClaims="false" is set on a virtual role, it is only evaluated when checking ACL-based access rights. Calls to principal.IsInRole for that virtual role will return false.

Securing the Edit and Admin UI

Optimizely supports hardening scenarios where the public site is separated from the CMS UI, or where the UI must be protected more aggressively on a public-facing application.

UI Hardening Patterns

Select a pattern to expand and see the code example.

Block access to edit/admin on a public front

Define authorization policies for CmsPolicyNames.CmsEdit and CmsPolicyNames.CmsAdmin to always deny access when running as a public front-end.

C#
var publicFront = _configuration.GetValue<bool?>("PublicFront"); if (publicFront.GetValueOrDefault(true)) { services.Configure<AuthorizationOptions>(o => o.AddPolicy(CmsPolicyNames.CmsAdmin, b => b.RequireAssertion(c => false))); services.Configure<AuthorizationOptions>(o => o.AddPolicy(CmsPolicyNames.CmsEdit, b => b.RequireAssertion(c => false))); }
Rename the UI path and adjust protected modules

Change the edit UI path to reduce attack surface. Use a relative path for same-host deployments or an absolute URL when the editor runs on a separate host or port.

C#
// Relative path services.Configure<UIOptions>(o => o.EditUrl = new Uri("~/newuipath/CMS/", UriKind.Relative)); // Absolute URL (separate host/port) services.Configure<UIOptions>(o => o.EditUrl = new Uri("https://securehost:8888/newuipath/CMS/", UriKind.Absolute)); // Adjust protected module root to match services.Configure<ProtectedModuleOptions>(o => o.RootPath = "~/newuipath/");

Cookie Security and HTTPS

Optimizely's security checklist emphasizes HTTPS and secure cookie configuration. The following example sets the cookie secure policy based on the current hosting environment - allowing non-HTTPS in development while enforcing it in production.

C#
services.ConfigureApplicationCookie(c => c.Cookie.SecurePolicy = _webHostingEnvironment.IsDevelopment() ? CookieSecurePolicy.SameAsRequest : CookieSecurePolicy.Always);

Note: SameSite=None cookies must also specify Secure, which requires HTTPS. This is enforced by modern browsers and is particularly relevant for OIDC callback cookies in cross-origin scenarios.

Content Access Rights

Content access rights (ACLs) determine who can read, edit, publish, or administer content. Administrators manage these through the CMS UI by creating users, groups, and roles and applying permissions to content items, blocks, media, folders, and language variations.

From a developer perspective, the key point is that content access control is not only an editorial UI feature - it is part of the overall security model and must be respected when implementing custom endpoints, tools, or integrations that read or modify content. Custom code that bypasses ACL checks creates security gaps even if the CMS UI appears restricted.

Conclusion

Security and access management in Optimizely CMS 12 rely on ASP.NET Core authentication and authorization, extended with CMS-specific capabilities:

  • Multiple authentication schemes support mixed-mode scenarios where editors and visitors authenticate differently.
  • Entra ID integration demonstrates how external identity can be synchronized into CMS users and roles.
  • Permissions to functions provide feature-level security with both UI-managed assignments and code-defined permissions.
  • Virtual roles enable runtime-based role membership, configurable through mapped roles or registered programmatically.
  • Decoupled setup patterns reduce the exposed surface area of edit and admin interfaces.
  • Cookie and HTTPS guidance hardens authentication in production environments.

A CMS 12 security design is typically successful when it treats authorization as a layered system: identity establishment (authentication), capability control (permissions and roles), and content-level enforcement (ACLs).