Exam Area: Content Area 3 – Website Implementation & Delivery (25%)
Source: https://academy.optimizely.com/student/path/3128969/activity/4970332 | /4970330
Published: Feb 10 / Feb 17, 2026
- 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).
There are 3 cooperating layers:
| Layer | Description |
|---|---|
| Authentication | Establishes user identity (principal) |
| Authorization | Evaluates if identity can access a capability |
| Access management | CMS-specific: roles, permissions-to-functions, virtual roles, ACLs |
services
.AddAuthentication()
.AddCookie("a-scheme")
.AddCookie("another-scheme");
// Set default scheme
services
.AddAuthentication(options => {
options.DefaultScheme = "another-scheme";
options.DefaultAuthenticateScheme = "another-scheme";
});
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;
});
}
Note: If
EPiServer.CMS.UI.AspNetIdentityis installed, avoid callingservices.AddCmsAspNetIdentity()in startup so the UI can synchronize users when setting access rights.
CMS 12 combines:
| Gate | Description |
|---|---|
| Feature gates | Admin features, APIs, diagnostics |
| UI gates | Edit/admin UI access |
| Content gates | Read/edit/publish rights on a content item |
Controls access to CMS features via Config → Permissions to functions.
// 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);
Virtual roles: membership is determined at runtime (not stored in the database). CMS ships with:
Anonymous, Everyone, Authenticated, CreatorCmsAdmins, CmsEditors, SearchAdmins, SearchEditors"EPiServer": {
"Cms": {
"MappedRoles": {
"Items": {
"SearchAdmins": {
"MappedRoles": [ "WebAdmins", "Administrators" ],
"ShouldMatchAll": "false"
}
}
}
}
}
Block access to edit/admin on the public front end:
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)));
}
services.ConfigureApplicationCookie(c => c.Cookie.SecurePolicy =
_webHostingEnvironment.IsDevelopment()
? Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest
: Microsoft.AspNetCore.Http.CookieSecurePolicy.Always);
Note:
SameSite=Nonemust specifySecure, which implies HTTPS.
public void GrantEditorPermission(ContentReference contentLink, string groupName)
{
var securityRepository = ServiceLocator.Current.GetInstance<IContentSecurityRepository>();
// 1. Get the current security descriptor
var securityDescriptor = securityRepository.Get(contentLink);
// 2. Create a writable clone
var writableDescriptor = securityDescriptor.CreateWritableClone() as IContentSecurityDescriptor;
// 3. Add or update access entry
var entry = new AccessControlEntry(groupName, AccessLevel.Edit | AccessLevel.Read | AccessLevel.Publish);
writableDescriptor.AddEntry(entry);
// 4. Save the modified security descriptor
securityRepository.Save(contentLink, writableDescriptor, SecuritySaveType.Replace);
}
AccessLevel.Read = 1
AccessLevel.Create = 2
AccessLevel.Edit = 4
AccessLevel.Delete = 8
AccessLevel.Publish = 16
AccessLevel.Administer = 64
GetChildren// In Program.cs
builder.Services
.AddCmsAspNetIdentity<ApplicationUser>()
.AddDefaultTokenProviders();
public class ApplicationUser : IdentityUser
{
public string FullName { get; set; }
}
ISynchronizingUserService used for? (Sync OIDC users/groups into the Optimizely database upon sign-in)CmsPolicyNames.CmsAdmin used for? (Policy gate controlling access to the Admin UI)ShouldMatchAll: false mean in mapped roles? (A user in ANY of the mapped roles is considered to be in the virtual role)SecurePolicy.Always require? (HTTPS – cookies are only sent over HTTPS connections)