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 document introduces the security model for developers who are new to Optimizely CMS 12. It focuses on 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 (cookie policy, HTTPS, editor/admin UI isolation).

Authentication in CMS 12

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

Mixed-mode authentication (multiple schemes)

Mixed-mode authentication is achieved by configuring multiple authentication schemes. The documentation example shows multiple cookie schemes:

services .AddAuthentication() .AddCookie("a-scheme") .AddCookie("another-scheme");

The documentation also shows how to set the default scheme explicitly:

services .AddAuthentication(options => { options.DefaultScheme = "another-scheme"; options.DefaultAuthenticateScheme = "another-scheme"; });

And an example where OpenID Connect is added and wired to a specific cookie scheme:

services .AddAuthentication() .AddCookie("a-scheme") .AddCookie("another-scheme") .AddOpenIdConnect("oidc", options => { options.SignInScheme = "another-scheme"; options.SignOutScheme = "another-scheme"; … });

Finally, the documentation shows signing out of a specific scheme:

public async Task<IActionResult> Logout() { await HttpContext.SignOutAsync("another-scheme"); return View(); }

These examples matter in CMS projects because editor/admin authentication requirements often differ from the public site's authentication requirements.

Integrating Entra ID (OpenID Connect)

In enterprise environments, CMS editor authentication is commonly delegated to an external identity provider. Optimizely provides documentation for integrating Entra ID (formerly Azure AD) via OpenID Connect using the ASP.NET OpenID Connect 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 documentation sample configuration includes cookie authentication with an OnSignedIn event that synchronizes the user and group membership into Optimizely:

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;

This synchronization step is central for CMS authorization because CMS access rules are applied using users/roles as known by Optimizely.

Authorization model

Once a user is authenticated, CMS authorization determines which protected resources and actions they can access.

CMS 12 combines:

  • Role checks (including virtual roles)
  • 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:

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

Permissions to functions

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

Checking permissions programmatically

The documentation provides two supported ways to query whether a user is permitted:

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

The documentation shows defining permissions using a class annotated with PermissionTypes so they are picked up automatically and surfaced in the admin UI:

[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

The documentation also shows applying a permission to an MVC controller using AuthorizePermission:

[AuthorizePermission("MyCustomPermissions", "EditSettings")] public class EditSettingsController: Controller { public ActionResult Index() { return View(); } }
Exposing permissions as roles for other systems

When a downstream system can validate roles but not permissions, the documentation shows registering a virtual role backed by a permission:

[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) {} public void Preload(string[] parameters) {} }

This pattern is useful when integrating CMS security with external infrastructure that only understands roles.

Virtual roles

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

Mapped roles in appsettings.json

The documentation includes an example of configuring mapped roles (a way to map one virtual role to one or more underlying roles):

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

The documentation also shows registering a virtual role type programmatically:

using EPiServer.Security; using EPiServer.DependencyInjection; using EPiServer.ServiceLocation; public class Startup { ... public void ConfigureServices(IServiceCollection services) { ... services.AddVirtualRole<MyVirtualRoleType>("MyVirtualRoleType"); } ... }

A key behavioral detail from the documentation: if SupportsClaims="false" on a virtual role, it is only evaluated when checking access rights based on ACLs; principal.IsInRole calls for that virtual role return false.

Securing the edit and admin UI

Optimizely documentation describes 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).

Block access to edit/admin on a public front

The documentation shows blocking access by defining authorization policies for CmsPolicyNames.CmsEdit and CmsPolicyNames.CmsAdmin so they deny access:

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

The documentation also shows changing the edit UI path:

services.Configure<UIOptions>(o => o.EditUrl = new Uri("~/newuipath/CMS/", UriKind.Relative));

And an example with an absolute URL (including a custom host/port):

services.Configure<UIOptions>(o => o.EditUrl = new Uri("https://securehost:8888/newuipath/CMS/", UriKind.Absolute));

Finally, it shows adjusting the root path for protected modules:

services.Configure<ProtectedModuleOptions>(o => o.RootPath = "~/newuipath/");

These measures are typically applied when the organization wants to reduce the attack surface of the admin/editor UI.

Cookie security and HTTPS

Optimizely's security checklist emphasizes HTTPS and secure cookie configuration. It includes an example for configuring secure cookies depending on environment:

services.ConfigureApplicationCookie(c => c.Cookie.SecurePolicy = _webHostingEnvironment.IsDevelopment() ? Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest : Microsoft.AspNetCore.Http.CookieSecurePolicy.Always);

The documentation also notes that cookie policy middleware can be used to apply policies to all cookies. Separately, the cookie usage documentation explains SameSite behavior changes and the requirement that SameSite=None must also specify Secure, which implies HTTPS.

Content access rights

Content access rights (ACLs) determine who can read, edit, publish, or administer content. Administrators manage these access rights 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.


Conclusion

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

  • Multiple authentication schemes support mixed-mode scenarios.
  • Entra ID integration demonstrates how external identity can be synchronized into CMS users/roles.
  • Permissions to functions provide feature-level security with both UI-managed assignments and code-defined permissions.
  • Virtual roles enable runtime-based role membership and can be configured through mapped roles or registered programmatically.
  • Decoupled setup patterns reduce the exposed surface area of edit/admin interfaces.
  • Cookie and HTTPS guidance harden 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).