<1> Setup Entra ID for CMS
Following the steps in the docs below to config Entra ID (formerly Azure AD) with CMS
https://support.optimizely.com/hc/en-us/articles/20767067525773
<2> Integrated CD with Entra ID into CMS site
Step 1: Install JwtBearer and content delivery API package:
Note: You can add the package directly to “.csproj” file to install like this
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.23" />
<PackageReference Include="EPiServer.ContentDeliveryApi.Cms" Version="3.9.0" />
<PackageReference Include="EPiServer.ContentDefinitionsApi" Version="3.9.0" />
<PackageReference Include="EPiServer.ContentManagementApi" Version="3.9.0" />
Step 2: Update Startup.cs file
var clientId = "YOUR CLIENT ID";
var clientSecret = "YOUR CLIENT SECRET";
var callbackPath = "/signin-oidc";
var azureAuthority = "https://login.microsoftonline.com/" + "YOUR TENANT ID" + "/v2.0";
var cookieSchema = "azure-cookie";
var challengeSchema = "azure";
var oidcConfig = new ConfigurationManager<OpenIdConnectConfiguration>($"{azureAuthority}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever()).GetConfigurationAsync().Result;
// Authentication Config
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = cookieSchema;
options.DefaultChallengeScheme = challengeSchema;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = azureAuthority,
ValidateIssuer = true,
ValidAudience = clientId,
ValidateAudience = true,
ValidateLifetime = true,
IssuerSigningKeys = oidcConfig.SigningKeys,
ValidateIssuerSigningKey = true
};
})
.AddCookie(cookieSchema, 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(challengeSchema, options =>
{
options.SignInScheme = cookieSchema;
//options.SignOutScheme = "azure-cookie";
options.ResponseType = OpenIdConnectResponseType.Code;
options.UsePkce = true;
options.ClientId = clientId;
options.Authority = azureAuthority;
options.CallbackPath = callbackPath;
options.ClientSecret = clientSecret;
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
{
// get the role from azure
RoleClaimType = "roles",
NameClaimType = "preferred_username",
ValidateIssuer = false
};
options.Events.OnRedirectToIdentityProvider = ctx =>
{
// 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;
};
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);
};
});
services.AddContentDeliveryApi(JwtBearerDefaults.AuthenticationScheme)
.WithFriendlyUrl()
.WithSiteBasedCors();
services.AddContentDefinitionsApi(JwtBearerDefaults.AuthenticationScheme, c =>
{
c.DisableScopeValidation = true;
});
services.AddContentManagementApi(JwtBearerDefaults.AuthenticationScheme, c =>
{
c.DisableScopeValidation = true;
});
Note: in case you want to use ScopeValidation, you first need to have the proper scope from Azure and set DisableScopeValidation = false
Then you have to match the .AddJwtBearer() with the claim that you get from the Token (id_token or access_token). and add options.MapInboundClaims = false; into it
This must be done and checked from your side so we are not suggesting if you don't want to have a complexible code for this part.
Step 3: Try to test the CD API
- Make sure you can log into the CMS using the Entra ID account from Section 1
- After accessing Admin mode, set up a page that is not visible to Everyone but Administrators only
- This page has ID = 9 so we will send a request to get this content in Postman but with no authentication first. It should return a 401 Unauthorized error as this content will not be visible for Everyone Query: https://localhost:5000/api/episerver/v3.0/content/9
- In this example, we will get the JWT to communicate with CD using the URL: https://login.microsoftonline.com/{TENNANTID}/oauth2/v2.0/token
For more information: https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth-ropc#authorization-request - In this case, we will use the id_token to authenticate. Decode the token we can see that has 3 roles
- Try to get the content again with the Bearer token and the content should return now
Thanks for reading. If you have any questions or problems, please send a request to support@optimizely.com and cc my email address anhtuan.hoang@optimizely.com
Please sign in to leave a comment.