IdentityServer4 Authentication for Sitecore


In my previous post, I showed how to use Sitecore Federated Authentication to enable login to your public site using a third-party OAuth/OpenID Connect provider such as Facebook and others. But many sites require a custom solution with a fully customizable identity provider. In this two-part series we are going to review how to implement a custom identity provider using IdentityServer4, an OAuth/OpenID Connect framework for ASP.NET Core (Sitecore Identity Server is based on IdentityServer4)—and how to integrate it with your site using Sitecore Federated Authentication.

For brevity, I have not included the using declaration blocks in the full class code samples.

Setting up the IdentityServer4 Provider

For this example, we are going to set up an IdentityServer4 Provider with in-memory services and stores since this is only a demo/test. We are going to use ASP.NET Core version 2.1 with IdentityServer4 version 2.5.0. We are not going to go in depth on IdentityServer4, OAuth, and OpenID Connect concepts since you can find detailed information about them in the official IdentityServer4 documentation. To set up our IdentityServer4 ASP.NET Core web application, follow the instructions provided in this very good guide by Scott Brady. This guide is based on version 2.0.2 of IdentityServer4, and you don't need to follow everything it says, so please take into account the following:

  • Use IdentityServer4 version 2.5.0 NuGet package.

  • On the User Interface section, you will see that there is a step to download and install the quickstart UI components. The PS command in the guide doesn't work anymore—you need to use the following command (as described in the UI GitHub site:

    iex ((New-Object System.Net.WebClient).DownloadString(''))
  • You don't need to follow the Client Application section and everything after it, since our client will be the Sitecore site, and we will cover that in Part 2 of this series.

  • As previously mentioned, this demo/test uses only in-memory services and stores. But if you need to create a fully working IdentityServer4 provider, I recommend implementing everything under the Entity Framework Core and ASP.NET Core Identity sections.

  • You can modify the look and feel of the UI components since they are standard ASP.NET Core MVC components. This means that you can make them match your Sitecore site's design and look-and-feel. We are not covering UI modification in this guide.

  • After everything has been set up and tested, make sure you deploy it to an independent site that is accessible from your Sitecore machine. ASP.NET Core gives you flexibility to deploy to IIS, Apache, and other web servers.

We are going to make the following modifications so it works correctly with our Sitecore integration:

  • Change the openIdConnectClient definition to this:

    new Client
        ClientId = "openIdConnectClient",
        ClientName = "Test OpenID Connect Client from Sitecore",
        AllowedGrantTypes = GrantTypes.Implicit,
        AllowedScopes = new List<lt;string>></lt;string>> {""},
        PostLogoutRedirectUris = new List<string> {""}

    We have basically removed the “role” and “customAPI.write” scopes from the example in the guide. We also added the RequireConsent flag set to false so it doesn't ask the user for consent to share her/his personal information provided by the allowed scopes, since this identity provider instance won't be accessible by third parties outside our application. Finally, we've included our Sitecore site's Redirect URIs. For the RedirectUri, make sure the provided URL has the path set to /signin-[identity provider id] format. In this specific case, we will use “is4” as the provider ID in the Sitecore Federated Authentication configuration (as we will see in Part 2 of this series). For the PostLogoutRedirectUri, the path MUST be set to /identity/postexternallogout, since this will redirect (after logout) to a special handler in Sitecore that performs a cleanup on the Sitecore side after logging out.

  • The default IdentityServer4 configuration will not match the PostLogoutRedirectUri specified in the Client configuration with the actual URI sent by Sitecore when logging out. Sitecore will send something like " /identity/postexternallogout ?ReturnUrl=[some path] &nonce=[nonce value]". We need to create a custom URI Validator so it ignores the query string when trying to validate the URI sent by the client:

    namespace Oshyn.Sample.IS4.Validators
        public class IgnoreQueryStringUriValidator : IRedirectUriValidator
            public Task<bool> IsRedirectUriValidAsync(string requestedUri, Client client)
                return Task.FromResult(StringCollectionCompareUris(client.RedirectUris, requestedUri));
            public Task<bool> IsPostLogoutRedirectUriValidAsync(string requestedUri, Client client)
                return Task.FromResult(StringCollectionCompareUris(client.PostLogoutRedirectUris, requestedUri));
            private bool StringCollectionCompareUris(IEnumerable<string> uris, string requestedUriString)
                foreach (var uriString in uris)
                    var requestedUri = new Uri(requestedUriString);
                    var comparisonUri = new Uri(uriString);
                    //Stripping the query string from the URIs
                    var filteredRequestedUri = requestedUri.GetLeftPart(UriPartial.Path);
                    var filteredComparisonUri = comparisonUri.GetLeftPart(UriPartial.Path);
                    //Compare stripped URIs
                    if (string.Equals(filteredComparisonUri, filteredRequestedUri, StringComparison.OrdinalIgnoreCase))
                        return true;
                return false;

    Register this URI Validator in the ConfigureServices method of Startup adding the AddRedirectUriValidator<IgnoreQueryStringUriValidator>() call to the IdentityServer4 build configuration:

    Startup.cs modifications
  • Add some test users to your Users store like this:

    new TestUser
        SubjectId = "0559BAE7-39A5-4A08-97EA-E3E6A28C0519", //It can be any ID
        Username = "testuser",
        Password = "password",
        Claims = new List<Claim>
            new Claim(JwtClaimTypes.Name, "Test User"),
            new Claim(JwtClaimTypes.Email, ""),
  • When a user logs out from your Sitecore site, it should automatically logout from IdentityServer4 without prompting, and also it should automatically redirect to the special PostLogoutRedirectUri specified in the Client configuration. To do this, open the Quickstart/Account/AccountOptions.cs file, and change the ShowLogoutPrompt and AutomaticRedirectAfterSignOut static flags to:

    AccountOptions.cs modifications
    public static bool ShowLogoutPrompt = false;
    public static bool AutomaticRedirectAfterSignOut = true;
  • Compile and deploy your IdentityServer4-based web application to a site/server that can be accessed by your Sitecore instance. For this example, we deployed to local IIS with host name Test by navigating to

Sitecore Site Configuration

Now that we've configured a custom identity provider using IdentityServer4 framework and ASP.NET Core, let's configure our Sitecore site, so it uses our custom identity provider for authentication. Note: if you read my previous article Authenticating Public Website Users With Sitecore 9.1+ and Facebook, you will see similar (and repeated) concepts, code and configurations. This is because we are using the same Sitecore Federated Authentication functionality to achieve this integration.

What do you need?

We are using Sitecore 9.1 Update-1 (9.1.1), so the following NuGet package list (with the libraries you will need for your module's .NET project) are based on what is compatible with Sitecore 9.1.1. For other versions, please check that you use the correct versions of the packages in your Sitecore installation bin directory:

Package Name Version
Sitecore.Abstractions 9.1.1
Sitecore.Kernel 9.1.1
Sitecore.Mvc 9.1.1
Sitecore.Owin.Authentication 9.1.1
Owin 1.0.0
Microsoft.AspNet.Identity.Core 2.2.1
Microsoft.AspNet.Identity.Owin 2.2.1
Microsoft.AspNet.Mvc 5.2.4
Microsoft.AspNet.Razor 3.2.7
Microsoft.AspNet.WebPages 3.2.7
Microsoft.Extensions.DependencyInjection.Abstractions 2.1.1
Microsoft.IdentityModel.Protocols 5.2.2
Microsoft.IdentityModel.Protocols.OpenIdConnect 5.2.2
Microsoft.Owin 4.0.0
Microsoft.Owin.Security 4.0.0
Microsoft.Owin.Security.OpenIdConnect 4.0.0

Also, don't forget to complete the IdentityServer4-based identity provider setup as discussed on Part 1 of this series. For this post, we are assuming the identity provider was deployed to a site accessible through

For brevity, I have removed the “using” declaration blocks from the code samples.

How to implement it

First, we will do some basic infrastructure to read our settings from the config patch file. The following class contains 4 properties that correspond to each of the settings we will be reading from our patch file:

namespace Oshyn.Auth.Settings
    public class Is4Settings
        private readonly BaseSettings _settings;
        private readonly string _prefix = "Oshyn.Auth.Is4.";
        public virtual string ClientId => _settings.GetSetting($"{_prefix}ClientId");
        public virtual string Authority => _settings.GetSetting($"{_prefix}Authority");
        public virtual string RedirectUri => _settings.GetSetting($"{_prefix}RedirectUri");
        public virtual string PostLogoutRedirectUri => _settings.GetSetting($"{_prefix}PostLogoutRedirectUri");
        public Is4Settings(BaseSettings baseSettings)
            _settings = baseSettings;

We also create an extension for BaseSettings, so it maps to our custom settings:

namespace Oshyn.Auth.Extensions
    public static class SettingsExtensions
        public static Is4Settings GetIs4Settings(this BaseSettings settings)
            Assert.ArgumentNotNull(settings, "settings");
            return new Is4Settings(settings);

Once we have these infrastructure classes and extensions set up, we can create our identity provider pipeline/processor. Here is the code, with an analysis below:

namespace Oshyn.Auth.Pipelines
    public class Is4ProviderProcessor : IdentityProvidersProcessor
        protected override string IdentityProviderName => "IS4";
        //List of scopes requested from IdentityServer4
        public Collection<string> Scopes { get; } = new Collection<string>();
        public Is4ProviderProcessor(
                FederatedAuthenticationConfiguration federatedAuthenticationConfiguration,
                ICookieManager cookieManager,
                BaseSettings baseSettings) :
            base(federatedAuthenticationConfiguration, cookieManager, baseSettings) { }
        protected override void ProcessCore(IdentityProvidersArgs args)
            Assert.ArgumentNotNull(args, "args");
            var identityProvider = GetIdentityProvider();
            //Using our extension to read our custom settings
            var is4Settings = Settings.GetIs4Settings();
            var openIdConnectAuthOptions = new OpenIdConnectAuthenticationOptions
                AuthenticationMode = AuthenticationMode.Passive,
                AuthenticationType = identityProvider.Name,
                Authority = is4Settings.Authority, //Value from settings
                Caption = identityProvider.Caption,
                ClientId = is4Settings.ClientId, //Value from settings
                CookieManager = CookieManager,
                PostLogoutRedirectUri = is4Settings.PostLogoutRedirectUri, //Value from settings
                RedirectUri = is4Settings.RedirectUri, //Value from settings
                ResponseType = "id_token", //We are using Implicit grant in IdentityServer4, so this must be "id_token"
                Scope = string.Join(" ", Scopes),
                UseTokenLifetime = false,
                Notifications = new OpenIdConnectAuthenticationNotifications
                    SecurityTokenValidated = message =>
                        var identity = message.AuthenticationTicket.Identity;
                        //This is based from a solution by Sean Sartell to allow Sitecore to correctly log out
                        identity.AddClaim(new Claim("id_token", message.ProtocolMessage.IdToken));
                        //Apply claim transformations
                            new TransformationContext(FederatedAuthenticationConfiguration, identityProvider)
                        message.AuthenticationTicket = new AuthenticationTicket(identity, message.AuthenticationTicket.Properties);
                        return Task.CompletedTask;
                    RedirectToIdentityProvider = message =>
                        //The following code is based from Sean Sartell solution for correct Sitecore logout redirection
                        var revokeProperties = message.OwinContext.Authentication.AuthenticationResponseRevoke?.Properties?.Dictionary;
                        if (revokeProperties != null && revokeProperties.ContainsKey("nonce") && revokeProperties.ContainsKey("id_token"))
                            var uri = new Uri(message.ProtocolMessage.PostLogoutRedirectUri);
                            var host = uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
                            var path = $"/{uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)}";
                            var nonce = revokeProperties["nonce"];
                            message.ProtocolMessage.PostLogoutRedirectUri = $"{host}/identity/postexternallogout?ReturnUrl={path}&nonce={nonce}";
                            //This is not in Sean Sartell's solution, but it is needed for IdentityServer4 to
                            //redirect from the identity provider back to the Sitecore postexternallogout handler
                            message.ProtocolMessage.IdTokenHint = revokeProperties["id_token"];
                        return Task.CompletedTask;

Let's quickly analyze this class:

  • There is a Scopes public property that accepts a Collection of strings. This is passed through the configuration patch file. The scopes must match the ones we have defined in the IdentityServer4 Client definition as seen in Part 1, and are configured in the config patch file that we'll analyze later.
  • In ProcessCore, we basically define the OpenID Connect configurations to connect to our IdentityServer4 provider:
    • We use the extension method defined previously to directly read our custom settings from the config patch file.
    • We create the options object, and pass the required fields. Notice that Authority, ClientId, PostLogoutRedirectUri and RedirectUri fields are pulled from our custom configuration values.
    • There are 2 async notifications (events) that we are implementing custom code: SecurityTokenValidated and RedirectToIdentityProvider.
    • In the SecurityTokenValidatedcode> event, we apply claims transformations. This should be done even if you don't have any transformations defined in your config patch file. Also, it adds the id_token to the authentication ticket. This is based on a solution by Sean Sartell to correctly log out on the Sitecore side after IdentityServer4 is logged out.
    • In the RedirectToIdentityProvider event, we use a code mostly based from Sean Sartell's solution to rebuild the PostLogoutRedirectUri. You might remember this in Part 1, where our IdentityServer4 provider expects a URI with a path set to /identity/postexternallogout. This URI is built with 2 parameters, ReturnUrl and nonce. The ReturnUrl is constructed from the PostLogoutRedirectUri setting in our config patch. The nonce value is taken from the revokeProperties set when a logout is triggered. These two parameters are required by the Sitecore.Owin.Authentication.Pipelines.Initialize.HandlePostLogoutUrl pipeline, that triggers a cleanup on the Sitecore side after IdentityServer4 redirects when logging out. Something that isn't included in Sean Sartell's solution, but it is required by IdentityServer4 to automatically redirect to the specified PostLogoutRedirectUri, is setting the "id_token_hint" parameter when triggering the logout in IdentityServer4. This is achieved by setting the IdTokenHint property in the protocol message, using the "id_token" value from the revokeProperties object.

Now we need to build our configuration patch file that needs to be deployed under App_Config/Include:

<configuration xmlns:patch=""
  <sitecore role:require="Standalone or ContentDelivery or ContentManagement">
    <!-- These are our custom settings -->
      <setting name="Oshyn.Auth.Is4.ClientId" value="openIdConnectClient" />
      <setting name="Oshyn.Auth.Is4.Authority" value="" /> <!-- This is our IdentityServer4 host name -->
      <setting name="Oshyn.Auth.Is4.RedirectUri" value="" />
      <setting name="Oshyn.Auth.Is4.PostLogoutRedirectUri" value="" />
        <!-- Here we define our provider processor pipeline -->
        <processor type="Oshyn.Auth.Pipelines.Is4ProviderProcessor, Oshyn.Auth" resolve="true">
          <scopes hint="list">
            <!-- These are the scopes requested from IdentityServer4 -->
            <scope name="openid">openid</scope>
            <scope name="profile">profile</scope>
            <scope name="email">email</scope>
        type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication">
      <identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
        <!-- This mapEntry is specific for our public website-->
        <mapEntry name="Public website"
                  type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication">
          <sites hint="list">
            <!-- Change it if you have a different name for the site in your <site name=""...> definition -->
          <identityProviders hint="list:AddIdentityProvider">
            <!-- This points to our custom IdentityServer4 "IS4" provider defined in the identityProviders section -->
            <identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='IS4']" />
          <!-- We are using our custom user builder to use the email address as the username -->
          <!-- Also we are setting isPersistentUser to false, so no new users are persisted in Sitecore -->
          <externalUserBuilder type="Oshyn.Auth.UserBuilders.CustomUserBuilder, Oshyn.Auth">
            <param desc="isPersistentUser">false</param>
      <identityProviders hint="list:AddIdentityProvider">
        <!-- External identity provider configuration -->
        <identityProvider id="IS4"
                          type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
          <param desc="name">$(id)</param>
          <param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
          <caption>Log in with IdentityServer4</caption>
          <!-- Make sure your icon file exists in your filesystem -->
          <!-- The following setting is very important, it triggers logout in IdentityServer4 -->
      <!-- General profile property mappings from the IdentityServer4 claims -->
      <propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
        <maps hint="list">
          <map name="Name to FullName"
               type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication"
            <data hint="raw:AddData">
              <source name="name" />
              <target name="FullName" />
          <map name="Emailaddress to Email"
               type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication"
            <data hint="raw:AddData">
              <source name="" />
              <target name="Email" />
          <map name="Comment"
               type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication"
            <data hint="raw:AddData">
              <source name="idp" />
              <target name="Comment" />

Let's quickly analyze this configuration patch file:

  • Our custom settings are defined under the <settings> section. These are read by our custom Is4Settings class using the extension method and accessed as shown in the Is4ProviderProcessor pipeline. For this example, we are explicitly configuring the RedirectUri and PostLogoutRedirectUri values with absolute URLs, since we haven't implemented an automated way to obtain the hostname. You can improve this and automatically obtain the hostname, and configure in these values only the relative paths. The path for RedirectUri MUST be /signin-[name_of_provider], and the name of the provider must match the name used for the IdentityProviderName property in the Is4ProviderProcessor class (in this example, IS4)
  • In pipelines/owin.identityProviders/processor, we point it to our Is4ProviderProcessor class. We pass a <scopes> list with the scopes we want to retrieve from IdentityServer4. In this example, we are retrieving all the scopes allowed from our custom IdentityServer4 provider (openid, profile, email). These scopes contain different types of claims that we can use in our Sitecore application.
  • In federatedAuthentication/identityProvidersPerSites/mapEntry, we map our public website (defined in your Site Configuration patch file as <site name=website ...>) to use our custom identity provider. The name must match the value used for the IdentityProviderName property in the Is4ProviderProcessor class. We also specify a custom user builder, so our external users' usernames are not random, but use the users' email addresses (this class is described later). Also, we set the isPersistentUser flag to false, so none of the external users are persisted in Sitecore's core database.
  • In federatedAuthentication/identityProviders/identityProvider, we configure the external identity provider. Make sure the triggerExternalSignOut flag is set to true, since this will allow IdentityServer4 to be logged out when a logout is triggered from your site.
  • In federatedAuthentication/propertyInitializer, we map IdentityServer4's claims to Sitecore profile properties, so then can be easily accessible using the Sitecore.Context.User.Profile properties.

The only thing already included in the configuration but not yet described is the custom user builder. This simple class will use the IdentityServer4 user's email and map it as the username for the virtual Sitecore user:

namespace Oshyn.Auth.UserBuilders
    public class CustomUserBuilder : DefaultExternalUserBuilder
        public CustomUserBuilder(bool isPersistentUser) :
            IsPersistentUser = isPersistentUser;
        public CustomUserBuilder(string isPersistentUser) :
            this(bool.Parse(isPersistentUser)) { }
        protected override string CreateUniqueUserName(UserManager<ApplicationUser> userManager, ExternalLoginInfo externalLoginInfo)
            Assert.ArgumentNotNull(userManager, "userManager");
            Assert.ArgumentNotNull(externalLoginInfo, "externalLoginInfo");
            var identityProvider =
            if (identityProvider == null)
                throw new InvalidOperationException("Unable to retrieve identity provider");
            return $"{identityProvider.Domain}\\{externalLoginInfo.Email}";

All the code and classes defined cover all the Federated Authentication configuration we must do for Sitecore to work with your IdentityServer4 custom identity provider defined in Part 1. But we need a way to test this functionality. To test this, we are going to create 2 simple Controller Renderings and Views to quickly handle login, user info and logout.

This is the Controller Rendering and View for the Login (don't forget to create the Controller Rendering definition in Sitecore under /sitecore/layout/Renderings):

namespace Oshyn.Auth.Controllers
    public class LoginController : Controller
        public ActionResult LoginProviderList()
            var corePipelineManager = ServiceLocator.ServiceProvider.GetService<BaseCorePipelineManager>();
            var args = new GetSignInUrlInfoArgs("website", "/");
            GetSignInUrlInfoPipeline.Run(corePipelineManager, args);
            return View("~/Views/Authentication/LoginProviderList.cshtml", args.Result);
@model IEnumerable<Sitecore.Data.SignInUrlInfo>
@foreach (var signIn in Model)
    using (Html.BeginForm(null, null, FormMethod.Post, new { @action = signIn.Href}))
        <button type="submit">
            <img src="@signIn.Icon"/>

This is the Controller Rendering and View for User Info and Logout button (also don't forget to create the Controller Rendering definition in Sitecore under /sitecore/layout/Renderings):

namespace Oshyn.Auth.Controllers
    public class UserInfoController : Controller
        public ActionResult UserInfo()
            return View("~/Views/Authentication/UserInfo.cshtml");
        public ActionResult Logout()
            return Redirect("/"); //Mostly irrelevant since redirection is handled from the HandlePostLogoutUrl pipeline.
<div id="user-info">
    <span>User name (test): @Sitecore.Context.User.Name</span>
    <span>Full Name: @Sitecore.Context.User.Profile.FullName</span>
    <span>Email: @Sitecore.Context.User.Profile.Email</span>
<div id="logout">
    @using (Html.BeginRouteForm(Sitecore.Mvc.Configuration.MvcSettings.SitecoreRouteName, FormMethod.Post))
        @Html.Sitecore().FormHandler("UserInfo", "Logout")
        <button type="submit">Logout</button>

In Sitecore (or Sitecore Rocks), create two pages; one for the Home, and another under the Home called Login. Assign the UserInfo rendering to the Home page, and the Login rendering to the Login page. Make sure your site definition points to your Home page. Publish the page and start testing. Go first to your Home page (in our specific case, we configured our server as, so we navigated to Make sure the Anonymous user is the one active, as shown in this screenshot:

Anyonymous user logged in screenshot

Navigate to your Login page (in our specific case, You will only see a button that will redirect you to the IdentityServer4 login page (enhancement idea: automatically redirect to the IdentityServer4 login page when navigating to /login, using the signIn.Href address)

Log in with IdentityServer4 button

Click on the Log in with IdentityServer4 button, and you will be automatically redirected to the IdentityServer4 login page. Let’s login using our “testuser”" user we created in Part 1 of this series:

IdentityServer4 login page screenshot

After successful login, you will be automatically redirected to the Home page. Now you will see the full logged-in user information:

logged-in user information screenshot

Click on the Logout button. You will be automatically redirected to IdentityServer4 again, but it will be very brief. If everything has been correctly configured as per this guide, you will be automatically redirected again to the Home page, and see the default\Anonymous user.