Configure federated authentication
You use federated authentication to let users log in to Sitecore through an external provider. Federated authentication requires that you configure Sitecore in a specific way, depending on which external provider you use. Configuring federated authentication involves a number of tasks:
Configure an identity provider
You must configure the identity provider you use. How you do this depends on the provider you use. The primary use case is to use Azure Active Directory (Azure AD). Authorize access to web applications using OpenID Connect and Azure Active Directory describes how Azure AD works.
To configure an identity provider:
-
Patch the
configuration/sitecore/federatedAuthentication/identityProviders
node by creating a new node with the nameidentityProvider
. -
Enter values for the
id
andtype
attributes. Thetype
must implement the abstract classSitecore.Owin.Authentication.Configuration.IdentityProvider
. Sitecore has a default implementation –Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider
. -
Under the node you created, enter values for the
param
,caption
,domain
, andtransformations
child nodes. -
Under the
configuration/sitecore/federatedAuthentication/identityProvidersPerSites
node, create a new node with namemapEntry
. -
Enter values for the
name
andtype
attributes. The value of thename
attribute must be unique for each entry. Thetype
must beSitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry
,Sitecore.Owin.Authentication
, or inherit from this. -
Enter
true
as the value of theresolve
attribute. -
Under the node you created, enter values for the
sites
(the list of sites where the provider(s) will work),identityProviders
(the list of providers), andexternalUserBuilder
child nodes. Entertrue
as the value of theresolve
attribute of eachexternalUserBuilder
node.NoteThis also applies for SXA sites.
Add code for the provider
You must create a new processor for the owin.identityProviders
pipeline.
To create a new processor:
-
Inherit the
Sitecore.Owin.Authentication.Pipelines.IdentityProviders.IdentityProvidersProcessor
class. -
Override the
IdentityProviderName
property with the name you specified for theidentityProvider
in the configuration. -
Override the
ProcessCore
method.
Integrate with the owin.identityProviders pipeline
Next, you must integrate the code into the owin.identityProviders
pipeline.
For example, this sample uses Azure AD as the identity provider:
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
<sitecore role:require="Standalone or ContentDelivery or ContentManagement">
<pipelines>
<owin.identityProviders>
<processor type="Sitecore.Owin.Authentication.YourIdentityProviders.AzureAd, YourAssembly" resolve="true" />
</owin.identityProviders>
</pipelines>
</sitecore>
</configuration>
Sitecore user name generation
User names must be unique across a Sitecore instance. You cannot use user names from different external providers as Sitecore user names because this does not guarantee that the user names are unique.
The DefaultExternalUserBuilder
class creates a sequence of user names for a given external user name. It then uses the first of these names that does not already exist in Sitecore. The values in the sequence depend only on the external username and the Sitecore domain configured for the given identity provider.
Map claims and roles
A provider issues claims and gives each claim one or more values. Sitecore reads the claims issued for an authenticated user during the external authentication process. You can restrict access to some resources to identities (clients or users) that have only specific claims.
An external user is a user that has claims. Mapping claims to roles allows the Sitecore role-based authentication system to authenticate an external user.
To map claims to roles:
-
Add an
<identityProvider>
node toconfiguration/sitecore/federatedAuthentication/identityProviders.
-
Add a
<transformations hint="list:AddTransformation">
node to the<identityProvider>
node. -
Add transformation nodes as child nodes.
For example, a transformation node looks like this:
RequestResponse<transformation type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication"> <sources hint="raw:AddSource"> <claim name="groups" value="f04b11c5-323f-41e7-ab2b-d70cefb4e8d0" /> <claim name="groups" value="40901f21-29d0-47ae-abf5-184c5b318471" /> </sources> <targets hint="raw:AddTarget"> <claim name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" value="Sitecore\Developer" /> </targets> <keepSource>true</keepSource> </transformation>
-
The
type
must inherit from theSitecore.Owin.Authentication.Services.Transformation
class. -
In this example, the transformation adds a claim with the name
http://schemas.microsoft.com/ws/2008/06/identity/claims/role
and the valueSitecore\Developer
to those identities that have two claims with the namegroup
and valuesf04b11c5-323f-41e7-ab2b-d70cefb4e8d0
and40901f21-29d0-47ae-abf5-184c5b318471
at the same time. -
keepSource==true
specifies that the original claims (twogroup
claims, in this example) will not be removed. The default isfalse
, and this means that if the transformation is successfully applied to the identity, then the original claims are replaced with the ones that are stated in the<targets>
nodes. -
keepSource==true
specifies that the original claims (twogroup
claims, in this example) will not be removed. In Sitecore, forSitecore.Owin.Authentication.Services.DefaultTransformation
,keepSource
has a default value offalse
, meaning that if the transformation is successfully applied to the identity, the original claims are replaced with the ones that are stated in the<targets>
nodes.NoteOn the Identity Server, for
Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation
,keepSource
has a default value oftrue
.
-
If you specify claims transformations in the sitecore/federatedAuthentication/sharedTransformations
node, these transformations are for all identity providers.
Map properties
You must map identity claims to the Sitecore user properties that are stored in user profiles.
The propertyInitializer
node, under the sitecore\federatedAuthentication
node, stores a list of maps. Each map has inner source and target nodes. These nodes have two attributes: name
and value
. You map properties by setting the value of these properties.
If a claim matches the name
attribute of a source node (and value
, if specified), the value
attribute of a user property specified by the name
attribute of a target node is set to the value of the matched claim (if the value
attribute is not specified in the target
node).
For example:
<propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
<maps hint="list">
<map name="status to UserStatus"
type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
<data hint="raw:AddData">
<source name="status" value="first" />
<target name="UserStatus" value="1" />
</data>
</map>
</maps>
</propertyInitializer>
In this example, the source name
and value
attributes are mapped to the UserStatus
target name and value 1
.
If you split up your configuration files, you must add the name
attribute to the map nodes to make sure that your nodes are unique across all the files. This is due to the way Sitecore config patching works.
Map custom users properties
You can map custom user properties in two places where the Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper
can store them:
-
If a user is virtual, regardless of whether the property is custom or not, it is stored in the
Sitecore.Security.Accounts.User
class as.RuntimeSettings.Properties
property. -
If a user is persistent, it is stored in the
Sitecore.Security.Accounts.User
class as.Profile.CustomProperties
property.
For debugging or development purposes, it is possible to look at these properties but they are private properties and are not expected to be accessed directly.
For custom functionality, where you need to access these properties, you can access them through the Sitecore.Security.Accounts.User
class Profile
property's indexer. This resolves the user property from the correct location regardless of whether the user is virtual or persistent.
Connect a user account
An account connection allows you to share profile data between multiple external accounts on one side and a persistent account on the other side. If a persisted user has roles assigned to them, federated authentication shares these with the external accounts.
Under the following circumstances, the connection to an account is automatic. Sitecore signs out the authenticated user, creates a new persistent or virtual account, and then authenticates it:
-
The user is already authenticated on the site.
-
The user signs in to the same site with an external provider.
-
There is not already a connection between an external identity and an existing, persistent account. In ASP.NET Identity,
signInManager.ExternalSignIn(...)
then returnsSignInStatus.Failure
.
To bind the external identity to an already authenticated account, you must override the Sitecore.Owin.Authentication.Services.UserAttachResolver
class using dependency injection. The following steps show an example of doing this:
-
Extend the
Sitecore.Owin.Authentication.Services.UserAttachResolver
class:RequestResponseusing System; using System.Threading.Tasks; using Microsoft.Owin; using Sitecore.Owin.Authentication.Services; using Sitecore.Text; namespace Sitecore.Owin.Authentication.Samples.Services { public class SampleUserAttachResolver : UserAttachResolver { public override UserAttachResolverResult Resolve(UserAttachContext context) { IFormCollection formData = Task.Run(async () => await context.OwinContext.Request.ReadFormAsync()).Result; string consentResult = formData["uar_action"]; UserAttachResolverResultStatus resultStatus; if (Enum.TryParse(consentResult, true, out resultStatus)) { return new UserAttachResolverResult(resultStatus); } string redirectUrl = new UrlBuilder("/dialogs/consent") { ["returnUrl"] = context.ReturnUrl }.ToString(); context.OwinContext.Response.Redirect(redirectUrl); return new UserAttachResolverResult(UserAttachResolverResultStatus.DelayedResolve); } } }
The
Resolve
method takesUserAttachContext
as a value argument, sends a request to the controller, and handles the answer from the controller that it calls. -
Create an endpoint by creating an MVC controller and a layout.
The MVC controller:
RequestResponseusing System.Web.Mvc; namespace Sitecore.Owin.Authentication.Samples.Controllers { public class ConsentController : Controller { public ActionResult Index() { this.ViewBag.User = this.HttpContext.User.Identity.Name; this.ViewBag.ReturnUrl = this.Request.Params["ReturnUrl"]; return this.View(); } } }
The layout:
RequestResponse<html xmlns="http://www.w3.org/1999/xhtml"> <head> <title>Consent window</title> </head> <body> <p> The @ViewBag.User user is already logged in. Would you like to attach to the user or create new record? </p> <br /> <form method="POST" action="@ViewBag.ReturnUrl"> <button type="submit" name="uar_action" value="attach">Attach</button> <button type="submit" name="uar_action" value="new">New</button> </form> </body> </html>
-
Register the extended class in Sitecore by creating a new service configurator class:
RequestResponseusing Microsoft.Extensions.DependencyInjection; using Sitecore.DependencyInjection; using Sitecore.Owin.Authentication.Samples.Services; using Sitecore.Owin.Authentication.Services; namespace Sitecore.Owin.Authentication.Samples.Infrastructure { public class ServicesConfigurator : IServicesConfigurator { public void Configure(IServiceCollection serviceCollection) { serviceCollection.AddSingleton<UserAttachResolver, SampleUserAttachResolver>(); } } }
-
Define the created class in a custom configuration file, by adding following node under the
<sitecore>
node:RequestResponse<services> <configurator type="Sitecore.Owin.Authentication.Samples.Infrastructure.ServicesConfigurator, Sitecore.Owin.Authentication.Samples" /> </services>
Programmatic account connection management
Sitecore uses the ASP.NET Identity for account connections, so account connections are handled in an identical way to the ASP.NET Identity API:
-
Retrieve a UserManager object from the Owin context:
RequestResponseusing Sitecore.Owin.Authentication.Extensions; // [...] IOwinContext context = HttpContext.Current.GetOwinContext(); UserManager<ApplicationUser> userManager = context.GetUserManager();
-
Use these methods for CRUD operations:
RequestResponseTask<IdentityResult> AddLoginAsync(ApplicationUser user, UserLoginInfo login); Task<IdentityResult> RemoveLoginAsync(ApplicationUser user, UserLoginInfo login); Task<IList<UserLoginInfo>> GetLoginsAsync(ApplicationUser user); Task<ApplicationUser> FindAsync(UserLoginInfo login);
Configure virtual and persistent users
Sitecore supports virtual users. When you authenticate users through external providers, Sitecore creates and authenticates a virtual user with proper access rights.
However, there are some drawbacks to using virtual users. User profile data cannot be persisted across sessions, as the virtual user profile exists only as long as the user session lasts. You should therefore create a real, persistent user for each external user. When a user uses external authentication for the first time, Sitecore creates and persists a new user, and binds this user to the external identity provider and the user ID from that provider. The next time that the user authenticates with the same external provider and the same credentials, Sitecore finds the already created and persisted user and authenticates it.
User builder
The identityProvidersPerSites/mapEntry
node contains an externalUserBuilder
node. Add a user builder like this:
-
Specify a class that inherits from
Sitecore.Owin.Authentication.Services.ExternalUserBuilder
. The user builder is responsible for creating a Sitecore user, based on the external user info.The default implementation that you configure to create either persistent or virtual users is based on the
isPersistentUser
constructor parameter:RequestResponse<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication"> <IsPersistentUser>true</IsPersistentUser> </externalUserBuilder>
When you implement the user builder, you must not use it to create a user in the database. It must only create an instance of the ApplicationUser
class.
Applying builder to site
Find mapEntry
within the identityProvidersPerSites
node of the site that you are going to define a user builder for, and specify the externalUserBuilder
node. For example:
<mapEntry type="Sitecore.FederatedAuthentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.FederatedAuthentication">
<sites hint="list">
<site>shell</site>
<site>admin</site>
<site>website</site>
</sites>
<identityProviders hint="list:AddIdentityProvider">mapEntry
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='AzureAd']" />
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='IdentityServer']" />
</identityProviders>
<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication">
<IsPersistentUser>true</IsPersistentUser>
</externalUserBuilder>
</mapEntry>
In the example above, Sitecore applies the builder to the shell
, admin
, and websites
sites.
The applied builders override the builders for the relevant site(s).
Generate sign-in links
When you have configured external identity providers for a Sitecore site, you can generate URLs for them through the getSignInUrlInfo
pipeline. This pipeline retrieves a list of sign-in URLs with additional information for each corresponding identity provider in this list.
You must only use sign-in links in POST requests.
To generate sign-in links:
-
Use the
getSignInUrlInfo
pipeline as in the following example:RequestResponseusing Sitecore.Pipelines.GetSignInUrlInfo; /* [...] */ var args = new GetSignInUrlInfoArgs(site: "website", returnUrl: "/"); GetSignInUrlInfoPipeline.Run(corePipelineManager, args);
The args.Result
contains a collection of Sitecore.Data.SignInUrlInfo
objects. These objects have the following properties:
-
Href
– the URL. -
IdentityProvider
– the name of the identity provider. You could, for example, use it as a CSS class for a link. -
Caption
– the caption of the identity provider. You should use this as the link text.
Use the Sitecore dependency injection to get an implementation of the BaseCorePipelineManager
class.