Go to the homepage
Powered bySitecore Search logo
Skip to main contentThe Single-Sign On in Content Hub page has loaded.

Single-Sign On in Content Hub

Context

As a company I want to centrally manage users and user access for the applications in my application landscape. One of these applications will be Content Hub.

In order to achieve this I want to enable SSO login on Content Hub to verify the user’s identity on my IDP of choice. Additionally I would like to control the user access level based on the user Claims received after a successful login into my IDP.

For this recipe we are using Microsoft Entra ID, previous called Microsoft Azure AD.

This recipe focuses on authentication through Content Hub, if you are using Sitecore Cloud Portal please review the Cloud Portal documentation.

Execution

Setting up the Single-Sign-On

An new enterprise application should be provisioned on Microsoft Entra ID. It is important to take note on the following details when configuring the Enterprise application.

KeyDetail
Allowed redirect url’sThe allowed redirect url should be configured as following to ensure a post back to Content Hub after login is allowed. Where contenthubinstance is your Content Hub URL - https://[contenthubinstance]/AuthServices-saml/Acs
Token configuration (SAML Claims)The following SAML Claims should be configured in order to fulfill the requirements in this recipe - Email and Security Groups.

On Microsoft Entra ID the number of groups emitted in a token is limited to 150 for SAML. For companies having a lot of security groups, this can be an issue as groups claims will be totally omitted for users having a membership in 150+ groups. As a workaround use user group assignment on your application if possible or apply a group filter as described here.

Navigate to the Content Hub settings page as a super user (Manage > Settings). Under Portal Configuration you can find the Authentication setting. As we are using SAML, we will need to setup the SAML provider, with the following details.

KeyDetail
email_claim_typehttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress. Unless configured differently on Microsoft Entra ID, this is the default for email addresses.
idp_entity_idThis can be found on the metadataxml generated by Microsoft Entra ID (entityID on the root node of the document)
metadata_locationThe link towards your metadata xml as provided by your enterprise application on Microsoft Entra ID.
provider_nameSAML (default)
sp_entity_idAs configured on Azure Entra ID (Application ID URI)
username_claim_typehttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/name. Unless configured differently on Microsoft Entra ID, this is the default for username.
"ExternalAuthenticationProviders": { "global_username_claim_type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "global_email_claim_type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "google": [], "microsoft": [], "saml": [ { "authentication_mode": "Passive", "email_claim_type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress", "idp_entity_id": "url to be found on metadataxml (entityID on root node)", "is_enabled": true, "metadata_location": "https://login.microsoftonline.com/link-to-your-metadataxml", "provider_name": "SAML", "sp_entity_id": "as configured on Azure Entra ID", "username_claim_type": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", "messages": { "signIn": "Sign in with SSO" } } ], "sitecore": [], "ws_federation": [], "yandex": [] },

As a first test, you can now try to login into Content Hub with SSO - Users are automatically created on first login.

You can bypass a Active configuration by forcing passive mode by appending the following query string. https://contenthubinstance/en-US/Account?forcePassive=true. This might be useful if you still like to login as regular user into Content Hub. eg.: to perform administrative tasks as a Super User.

In order to see what has been transmitted as a Claim it is good to have a SAML tracer extension on your browser. eg: SAML, WS-Federation and OAuth 2.0 tracer

Automated Claim to Content Hub Security Groups mapping

As we want to centrally manage security group membership, Microsoft Entra ID is a good place to do this. However, we do also want this being reflected into our Content Hub Application. In order to do this we will need to read-out any group Claims that will be emitted at login and do some AD group to Content Hub mapping magic.

The two necessary parts to make this working on Content Hub are:

  • A setting to configure the desired group mapping
  • A sign-in script that reads out the group claims at login and applies the mapping so the user has the appropriate security rights after login.

We could just go ahead and create a new setting on the Content Hub from the interface (Manage > Settings).

However, you might have multiple Content Hub environments (DEV, UAT, PROD) and it might be required to have a different mapping configurations per environment as the AD groups are potentially different for each environment. In order to create a environment specific setting we will need to do a POST request to create to entity with your tool of choice (eg.: POSTMAN). This is not possible through the interface as M.Setting.EnvironmentSpecific is marked as set-once.

Endpoint: https://contenthubinstance/api/entities Method: POST Body:

{ "identifier": "M.Setting.SSOMapping", "properties": { "M.Setting.Name": "SSOMapping", "M.Setting.EnvironmentSpecific": true, "M.Setting.Value": {} }, "relations": { "SettingCategoryToSettings": { "parent": { "href": "https://[contenthubinstance]/api/entities/759" } } }, "entitydefinition": { "href": "https://[contenthubinstance]/api/entitydefinitions/M.Setting" } }

M.Setting.EnvironmentSpecific has been set to true at creation time as this is the only moment where we can set this property.

Once you have executed the POST command, you should have a new setting available under Security > SSOMapping. Now it is a good time to populate the mapping setting with some default values.

Example:

{ "groupMapping": [ { "label": "SuperUser mapping", "external": "2db23085-7778-4d35-b2bb-3869fbc47552", "internalGroups": [ "superuser" ] } ], "claimType": "http://schemas.microsoft.com/ws/2008/06/identity/claims/groups", "defaultGroups": [ "Everyone" ] }

The external property indicates the external group that comes in through the Claims emitted by Entra ID, the internal property indicates to which Content Hub usergroup it will be mapped to.

The only thing left to make our automated mapping working is to add a user sign-in script. Navigate to Scripts under the Manage section and add a new sign-in script.

Scripts under the Manage section

Next copy the following mapping script in your User sign-in script. Build, Publish and enable the script.

using System.Linq; using Stylelabs.M.Sdk; using Stylelabs.M.Scripting.Types.V1_0.User; using Stylelabs.M.Scripting.Types.V1_0.User.SignIn; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Collections.Generic; using System.Threading.Tasks; const string ROLES_MAP_SETTING_CATEGORY = "Security"; const string ROLES_MAP_SETTING_NAME = "SSOMapping"; class GroupMapping { public string label {get;set; } public string external {get; set;} public string[] internalGroups {get; set;} } class MappingSetting { public List<GroupMapping> groupMapping {get;set;} public string ClaimType {get;set;} public List<string> defaultGroups {get;set;} } try { await RunScriptAsync(); } catch(Exception ex) { MClient.Logger.Error(ex); } public async Task RunScriptAsync() { if(Context.AuthenticationSource == AuthenticationSource.Internal) { MClient.Logger.Info($"Authentication source is Internal. Exiting."); return; } MClient.Logger.Info($"Script called for user {Context.User.Id}"); MClient.Logger.Debug($"User claims: {string.Join(",\r\n", Context.ExternalUserInfo.Claims.Select(c => $"{c.Type}: {c.Value}"))}"); var roleMapSettings = await GetSettingValueAsync<JObject>(ROLES_MAP_SETTING_CATEGORY, ROLES_MAP_SETTING_NAME); if(roleMapSettings == null) { MClient.Logger.Error($"Could not load role map settings. Exiting."); return; } var mappingSetting = roleMapSettings.ToObject<MappingSetting>(); var groupClaims = GetClaimValues(mappingSetting.ClaimType); if(!groupClaims.Any()) { MClient.Logger.Info($"No claims of type {mappingSetting.ClaimType}. Exiting."); return; } MClient.Logger.Debug($"Found {groupClaims.Count()} claim(s). {string.Join(",\r\n", groupClaims)}"); var groupMaps = mappingSetting.groupMapping.Where(m => groupClaims.Contains(m.external)); MClient.Logger.Info($"Found {groupMaps.Count()} group map(s). For roles\r\n{string.Join(", ", groupMaps.Select(rm => rm.external))}"); var userGroupNames = groupMaps.SelectMany(rm => rm.internalGroups).ToList(); MClient.Logger.Info($"Found {userGroupNames.Count()} user group(s).\r\n{string.Join(", ", userGroupNames)}"); //Adding the default usergroups to the list userGroupNames.AddRange(mappingSetting.defaultGroups); var groupIds = await GetIdsForGroupNamesAsync(userGroupNames); await AddUserGroupsToUserAsync(groupIds.Distinct()); } #region functions public IEnumerable<string> GetClaimValues(string type) { return Context.ExternalUserInfo.Claims.Where(c => c.Type == type).Select(c => c.Value); } public async Task<T> GetSettingValueAsync<T>(string category, string name) { var settingEntity = await MClient.Settings.GetSettingAsync(category, name).ConfigureAwait(false); return settingEntity.GetPropertyValue<T>("M.Setting.Value"); } public async Task<IEnumerable<long>> GetIdsForGroupNamesAsync(IEnumerable<string> groupNames) { var query = Query.CreateQuery(entities => from e in entities where e.DefinitionName == "Usergroup" && e.Property("GroupName").In(groupNames) select e); var queryResult = await MClient.Querying.QueryIdsAsync(query); return queryResult.Items; } public async Task AddUserGroupsToUserAsync(IEnumerable<long> userGroupIds) { var userGroupToUserRelation = await Context.User.GetRelationAsync<IChildToManyParentsRelation>("UserGroupToUser"); //REPLACE the usergroups as per mapping result //Update usergroup to user relation userGroupToUserRelation.SetIds(userGroupIds); //Update usergroup configuration on the user. var userGroupConfigurationProperty = Context.User.GetPropertyValue<JToken>("UserGroupConfiguration"); if(userGroupConfigurationProperty == null){ Context.User.SetPropertyValue("UserGroupConfiguration",JToken.Parse("{\"combine_method\": \"Any\",\"user_group_ids\": [],\"children\": []}")); userGroupConfigurationProperty = Context.User.GetPropertyValue<JToken>("UserGroupConfiguration"); } if (userGroupConfigurationProperty != null) { var jGroupsIds = JToken.FromObject(userGroupIds); MClient.Logger.Info($"Applying following user groups: {jGroupsIds} for user with id {Context.User.Id}"); userGroupConfigurationProperty.SelectToken("user_group_ids")?.Replace(jGroupsIds); } else { MClient.Logger.Info("UserGroupConfiguration property is null somehow"); } await MClient.Entities.SaveAsync(Context.User); } #endregion

If everything works as expected, users that login into Content Hub should be automatically assigned to the appropriate user groups, based on group information emitted by your IDP. Important to note here is that the script will adjust your membership situation each time a login is performed, this includes security group movements as well.

Insights

Troubleshooting

  • Setup SSO first and see if it works before implementing and enabling the mapping script
  • Ensure that all required claims are configured and send to Content Hub. Use a browser extension like SAML tracer to check what claims your are receiving. A common error is when the email claim (mandatory) is wrongly configured in your portal settings or not configured on your IDP.
  • Username / email should be a unique combination. Make sure there are no left over Content Hub users already consuming the username or email as this will violate this rule. As a result you won’t be able to login using SSO.
  • When getting a login error check from where this is originating, often your login is already blocked before being redirected to Content Hub
  • It is good practice to have a Content Hub managed account (super user) to be able to log-in to the system (only) in case of disaster (eg.: SSO stops working or you lost your access rights due to a claims mapping error)
  • If you don’t have the appropriate security groups assigned after login though SSO - make sure you have logging on you sign-in script so you can check what is going on and make sure claim mapping has been setup correctly.

For customers that do not want to use SSO

Although we highly recommend the usage of SSO on your Content Hub solution. You could also use Content Hub without a SSO provider configured. Be aware of the following implications on user management for your implementation.

  • User and Access management will need to be handled within Content Hub, as this requires Content Hub knowledge, you will need to have a trained super users to perform this task. Depending on the amount of users you are aiming to onboard on your system, this might become a huge task.
  • Security rights in Content Hub are driven by assigning the correct security groups to a user. Often this is a set of Security Groups. You will need to be aware of what combination is necessary for a particular user, this is something that will need to be done for every user you are managing.
  • Adjustments to the security groups like introducing a new one, will also require you to adjust assigned user groups on the affected list of users. A task that can easily be automated with a sign-in script.
  • Having user management directly on Content Hub might impose a security risk as opposed to have access management managed centrally on for instance Microsoft Entra ID. For instance: if someone is switching a team of leaves the company, then this needs to be reflected on user management on Content Hub as well. A task that is often forgotten.

© Copyright 2025, Sitecore. All Rights Reserved

Legal

Privacy

Get Help

LLM