IdentityServer4 Authentication for Sitecore - Part 1

Sep 30, 2019
Byron Calisto

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('https://raw.githubusercontent.com/IdentityServer/IdentityServer4.Quickstart.UI/master/getmaster.ps1'))
    
  • 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>> {"https://sc911.oshyn.com/signin-is4"},
        PostLogoutRedirectUris = new List<string> {"https://sc911.oshyn.com/identity/postexternallogout"}
    }
    
    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 "https://sc911.oshyn.com/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
    services.AddIdentityServer()
        .AddInMemoryClients(Clients.Get())
        .AddInMemoryIdentityResources(Resources.GetIdentityResources())
        .AddInMemoryApiResources(Resources.GetApiResources())
        .AddTestUsers(Users.Get())
        .AddDeveloperSigningCredential()
        .AddRedirectUriValidator<IgnoreQueryStringUriValidator>();
    
  • 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, "test@example.com"),
        }
    }
    
  • 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-is4.oshyn.com. Test by navigating to https://test-is4.oshyn.com/Account/Login

In part 2 we will discuss the actual integration with Sitecore using Federated Authentication.

References