Sitecore Single Sign-On using Custom Identity Provider
In the previous blog, we discussed Azure AD Integration with Sitecore for content management. Now in this blog, we are going to discuss how we can allow the end users to log in through SSO. Single Sign On allows users to enter credentials only one time instead of entering the credentials on each application.
Sitecore identity server that comes with Sitecore 9.1 allows you to log in through an external identity provider like Azure Active Directory, Facebook, Apple, or Google. It is built on Federation Authentication. Sitecore Identity.
Below are the steps:
Step 1: Configure OpenID Connect
Create a pipeline processor to configure OpenID connect to talk to custom identity providers.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.IdentityModel.Protocols.OpenIdConnect; | |
using Microsoft.Owin.Infrastructure; | |
using Microsoft.Owin.Security.Notifications; | |
using Microsoft.Owin.Security.OpenIdConnect; | |
using Owin; | |
using Sitecore.Abstractions; | |
using Sitecore.Owin.Authentication.Configuration; | |
using Sitecore.Owin.Authentication.Pipelines.IdentityProviders; | |
using Sitecore.Owin.Authentication.Services; | |
using System; | |
using System.Collections.ObjectModel; | |
using System.Threading.Tasks; | |
namespace XXX | |
{ | |
public class DemoProjectIdentityProvider : IdentityProvidersProcessor | |
{ | |
protected override string IdentityProviderName => "DemoProjectIdentityProvider"; | |
protected BaseLog Log { get; } | |
public Collection<string> Scopes { get; } = new Collection<string>(); | |
private readonly ICookieManager cookieManager; | |
readonly string clientId = Sitecore.Configuration.Settings.GetSetting("ClientId"); | |
readonly string redirectUri = Sitecore.Configuration.Settings.GetSetting("RedirectUri"); | |
readonly string clientSecret = Sitecore.Configuration.Settings.GetSetting("ClientSecret"); | |
readonly string authority = Sitecore.Configuration.Settings.GetSetting("Authority"); | |
readonly string postLogoutRedirectUri = Sitecore.Configuration.Settings.GetSetting("PostLogoutRedirectUri"); | |
readonly string errorUri = Sitecore.Configuration.Settings.GetSetting("ErrorMessage"); | |
public DemoProjectIdentityProvider( | |
FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, | |
ICookieManager cookieManager, | |
BaseSettings settings) : base(federatedAuthenticationConfiguration, cookieManager, settings) | |
{ | |
this.cookieManager = cookieManager ?? throw new ArgumentNullException(nameof(cookieManager)); | |
} | |
protected override void ProcessCore(IdentityProvidersArgs args) | |
{ | |
var authenticationType = this.GetAuthenticationType(); | |
var identityProvider = this.GetIdentityProvider(); | |
var saveSigninToken = identityProvider.TriggerExternalSignOut; | |
var oidcOptions = this.SetupOidcOptions(authenticationType, saveSigninToken); | |
args.App.UseOpenIdConnectAuthentication(oidcOptions); | |
} | |
public OpenIdConnectAuthenticationOptions SetupOidcOptions( | |
string authenticationType, | |
bool saveSigninToken) | |
{ | |
var oidcOptions = new OpenIdConnectAuthenticationOptions | |
{ | |
AuthenticationType = authenticationType, | |
Authority = authority, | |
ClientId = clientId, | |
ClientSecret = clientSecret, | |
ResponseType = OpenIdConnectResponseType.IdTokenToken, | |
RedirectUri = redirectUri, | |
PostLogoutRedirectUri = postLogoutRedirectUri, | |
Scope = OpenIdConnectScope.OpenIdProfile + " " + OpenIdConnectScope.OfflineAccess, | |
SaveTokens = true, | |
Notifications = new OpenIdConnectAuthenticationNotifications | |
{ | |
RedirectToIdentityProvider = this.RedirectToIdentityProviderAsync, | |
SecurityTokenValidated = this.SecurityTokenValidatedAsync | |
}, | |
TokenValidationParameters = | |
{ | |
SaveSigninToken = saveSigninToken | |
}, | |
}; | |
return oidcOptions; | |
} | |
private Task RedirectToIdentityProviderAsync( | |
RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> | |
notification) | |
{ | |
var owinContext = notification.OwinContext; | |
var protocolMessage = notification.ProtocolMessage; | |
var user = Sitecore.Context.User; | |
if (protocolMessage.RequestType == OpenIdConnectRequestType.Authentication) | |
{ | |
protocolMessage.RedirectUri = postLogoutRedirectUri; | |
} | |
if (protocolMessage.RequestType == OpenIdConnectRequestType.Logout) | |
{ | |
protocolMessage.PostLogoutRedirectUri = postLogoutRedirectUri; | |
protocolMessage.IdTokenHint = this.GetIdTokenHint(owinContext); | |
} | |
return Task.CompletedTask; | |
} | |
private Task SecurityTokenValidatedAsync(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification) | |
{ | |
var identityProvider = this.GetIdentityProvider(); | |
var identity = notification.AuthenticationTicket.Identity; | |
foreach (var current in identityProvider.Transformations) | |
{ | |
current.Transform(identity, new TransformationContext(this.FederatedAuthenticationConfiguration, identityProvider)); | |
} | |
return Task.CompletedTask; | |
} | |
} | |
} |
Step 2: Patch File
Create a patch file that will register a custom Sitecore identity.
Step 3: Login Button Functionality
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?xml version="1.0" encoding="utf-8" ?> | |
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/"> | |
<sitecore> | |
<settings> | |
<setting name="ClientId" value="XXX" /> | |
<setting name="RedirectUri" value="XXX" /> | |
<setting name="ClientSecret" value="XXX" /> | |
<setting name="Authority" value="XXX" /> | |
<setting name="PostLogoutRedirectUri" value="XXX" /> | |
<setting name="ErrorMessage" value="XXX" /> | |
</settings> | |
<federatedAuthentication type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication"> | |
<identityProvidersPerSites hint="list:AddIdentityProvidersPerSites"> | |
<mapEntry name="sites with extranet domain" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication" resolve="true" patch:instead="*[@name='sites with extranet domain']"> | |
<sites hint="list"> | |
<site>DemoWebsite</site> | |
</sites> | |
<identityProviders hint="list:AddIdentityProvider"> | |
<identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='DemoProjectIdentityProvider']" /> | |
</identityProviders> | |
<externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication" resolve="true"> | |
<IsPersistentUser>false</IsPersistentUser> | |
</externalUserBuilder> | |
</mapEntry> | |
</identityProvidersPerSites> | |
<identityProviders> | |
<identityProvider id="DemoProjectIdentityProvider" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication"> | |
<param desc="name">$(id)</param> | |
<param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" /> | |
<caption>Go to login</caption> | |
<domain>extranet</domain> | |
<triggerExternalSignOut>true</triggerExternalSignOut> | |
<transformations hint="list:AddTransformation"> | |
<transformation name="Idp Claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication" /> | |
<transformation name="set id_token claim" type="Sitecore.Owin.Authentication.Services.SaveIdTokenInClaim, Sitecore.Owin.Authentication" /> | |
<transformation type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication"> | |
<sources hint="raw:AddSource"> | |
<claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" /> | |
</sources> | |
<targets hint="raw:AddTarget"> | |
<claim name="Email" /> | |
</targets> | |
<keepSource>false</keepSource> | |
</transformation> | |
</transformations> | |
</identityProvider> | |
</identityProviders> | |
<propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication"> | |
<maps hint="list"> | |
<map name="set SSO FullName" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication" resolve="true" patch:source="Project.NAC.AzureAD.config"> | |
<data hint="raw:AddData"> | |
<source name="full_name" /> | |
<target name="FullName" /> | |
</data> | |
</map> | |
<map name="Given Name" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication" resolve="true"> | |
<data hint="raw:AddData"> | |
<!--<source name="given_name" />--> | |
<source name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" /> | |
<target name="Name" /> | |
</data> | |
</map> | |
</maps> | |
</propertyInitializer> | |
</federatedAuthentication> | |
<pipelines> | |
<owin.identityProviders> | |
<processor type="XXX.DemoProjectIdentityProvider, XXX" resolve="true" id="DemoProjectIdentityProvider"> | |
<scopes hint="list"> | |
<scope name="openid">openid</scope> | |
<scope name="profile">profile</scope> | |
<scope name="offline_access">offline_access</scope> | |
</scopes> | |
</processor> | |
</owin.identityProviders> | |
</pipelines> | |
</sitecore> | |
</configuration> |
Step 3: Login Button Functionality
Now on clicking on the login button, you need to redirect to SSO so for this controller add below code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// In Controller | |
public override ActionResult Index() | |
{ | |
var model = this.GetModel() as LoginModel; | |
var args = new GetSignInUrlInfoArgs("DemoWebsite", "/"); //DemoWebsite name must be same which you have mentioned in site in patch file and mention postback URL | |
GetSignInUrlInfoPipeline.Run(_pipelineManager, args); | |
model.Result = args.Result; | |
return View(model); | |
} | |
// In View | |
@foreach (var signIn in Model.Result) | |
{ | |
using (Html.BeginForm(null, null, FormMethod.Post, new { @action = signIn.Href })) | |
{ | |
<button type="submit"> | |
<img src="@signIn.Icon" /> | |
Login | |
</button> | |
} | |
} |
SXA: In the SXA website you need to add "loginpage" and "requireLogin" properties in "Other properties" section.
Step 4: Secure Page
There could be a possibility that you want to secure only a few pages so in this case on that page restrict "extranet\Anonymous" user.
Step 5: Logout
On the logout button click you can logout virtual user as mentioned below:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[HttpPost] | |
[ValidateAntiForgeryToken] | |
public ActionResult LogOut() | |
{ | |
Sitecore.Security.Authentication.AuthenticationManager.Logout(); | |
Session.Abandon(); | |
return RedirectToAction("/"); | |
} |
Comments