allBlogsList

Sitecore IdentityServer + Okta - Federated Authentication

Federated Authentication via Okta

There are many posts out there on how to configure federated authentication for content authors in Sitecore. Back before the days of Sitecore IdentityServer, this process could be a bit tricky. Sitecore IdentityServer makes it exceedingly simple to integrate a new Identity Provider (IDP) into the equation for authentication of your content authors. As noted in the Sitecore Documentation, successful integration into Sitecore IdentityServer can be accomplished via a configuration file and a provider implementation. Once the files are written, they should be deployed to IdentityServer NOT to Sitecore. In this post, I will go over what the IdentityServer code looks like for an Okta integration, and then show how to configure Okta to respond to IdentityServer. Let's start with configuring Okta.

If you navigate to the Okta site, you can set up a trial instance of Okta that is good for 30 days. This is quite useful in testing your integration if you don't have one that the client will let you use. The first thing to be done in Okta is to add a new application. Our application platform is Web, and our sign-on method is OpenID Connect.

OktaApp

Once you click create, you will be prompted to provide some details. Give the application a name (e.g. Sitecore), and provide a login URI. The login URI must be a fully qualified URI back to your identity server: https://domain.identityserver.com/signin-idsrv. NOTE: Remember what the end of that path is, as you will need it when you start writing code.

Once you save the application, you will be able to see the details. We are not quite there yet, though. Edit your application, and update grant types to use Implicit (Hybrid). Also, take note of the Client ID and Client Secret. We will need those to configure Identity Server.

OktaApplication

Lastly, pop over to the Sign On tab, and edit the OpenID Connect ID Token. We need to modify the Group claims filter, so we get them back in our authentication request.

OktaGroups

Now we have our authentication application configured. You'd like to think that is it, but there is a bit more on the Okta side. We need to tell Okta to allow the groups claim as an authentication scope on API requests. To do that, we pop over to the security section: Security -> API -> Authorization Servers. In my case, the server I am messing with is the default. Select your server, and then navigate to the Scopes tab. From here, we need to add the groups scope to the list.

OktaScopes

One more thing... We also need to add a groups claim. Click on the Claims tab, and add a new claim for groups. You can configure when it is included on the request, but I just put it on there always. Also, note the regular expression I am using; this will return all groups on the request. If you have a mechanism for just getting Sitecore groups (e.g. prefixing with Sitecore_), you can use that to reduce the payload of your authentication response.

OktaClaims

Those last two steps plagued me a bit for a while. Okta was not at all happy about me requesting groups in my authentication calls. Shout out to @Immanuel79 for pointing me in the right direction on that one.

Okay! Okta is done. Now we pop over to Sitecore to hook it up. First things first, we need to add a configuration to IdentityServer to tell Sitecore that Okta is available as an external authentication provider. The easy way to do this is to copy the AzureAD configuration that comes out-of-the-box and then make your updates. Too much work? Get it from the GitHub link below:

https://github.com/bic742/Sitecore-IdentityServer-Okta

Here are the important snippets:

    <ExternalIdentityProviders>
      <IdentityProviders>
        <Okta type="Sitecore.Plugin.IdentityProviders.IdentityProvider, Sitecore.Plugin.IdentityProviders">
          <AuthenticationScheme>IdS4-Okta</AuthenticationScheme>
          <DisplayName>IdentityServer Okta Identity Provider</DisplayName>
          <Enabled>true</Enabled>
          <ClientId></ClientId> <!-- get this from your Okta Application -->
          <ClientSecret></ClientSecret> <!-- get this from your Okta Application -->
          <Authority></Authority> <!-- this is the url to your Okta instance -->
      ...
    </ExternalIdentityProviders>

All of these are important, but the three items with comments are critical. In conjunction with the configuration, we also need to implement the identity provider logic.

using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Okta.FederatedAuth.Configuration;
using Sitecore.Framework.Runtime.Configuration;

namespace Okta.FederatedAuth
{
    public class ConfigureSitecore
    {
        private readonly ILogger<ConfigureSitecore> _logger;
        private readonly AppSettings _appSettings;

        public ConfigureSitecore(ISitecoreConfiguration scConfig, ILogger<ConfigureSitecore> logger)
        {
            this._logger = logger;
            this._appSettings = new AppSettings();
            scConfig.GetSection(AppSettings.SectionName);
            scConfig.GetSection(AppSettings.SectionName).Bind((object)this._appSettings.OktaIdentityProvider);
        }

        public void ConfigureServices(IServiceCollection services)
        {
            var oktaProvider = this._appSettings.OktaIdentityProvider;
            if (!oktaProvider.Enabled)
                return;
            this._logger.LogDebug("Configure '" + oktaProvider.DisplayName + "'. AuthenticationScheme = " + oktaProvider.AuthenticationScheme + ", ClientId = " + oktaProvider.ClientId, Array.Empty<object>());
            new AuthenticationBuilder(services).AddOpenIdConnect(oktaProvider.AuthenticationScheme, oktaProvider.DisplayName, (Action<OpenIdConnectOptions>)(options =>
            {
                options.SignInScheme = "idsrv.external";
                options.ClientId = oktaProvider.ClientId;
                options.ClientSecret = oktaProvider.ClientSecret;
                options.Authority = oktaProvider.Authority;
                options.CallbackPath = "/signin-idsrv";

                options.Scope.Add("groups");

                options.Events.OnAuthenticationFailed += (Func<AuthenticationFailedContext, Task>)(context =>
                {
                    return Task.CompletedTask;
                });

                options.Events.OnRedirectToIdentityProvider += (Func<RedirectContext, Task>)(context =>
                {
                    var first = context.HttpContext.User.FindFirst("idp");
                    if (string.Equals(first?.Value, oktaProvider.AuthenticationScheme, StringComparison.Ordinal))
                        context.ProtocolMessage.Prompt = "select_account";
                    return Task.CompletedTask;
                });
            }));
        }
    }
}

There are some key things in this file to take note of:

  • ClientId, ClientSecret, and ClientAuthority are all pulled from our configuration
  • The URI we used for your login URI when you configured the application in Okta is referenced here.
  • options.Scope.Add("groups") - we need this to get groups back

The last thing to do is convert our Okta groups to roles. This is accomplished by updating the configuration. An example transformation mapping looks like this:

<ClaimsTransformation3 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
    <Claim1 type="groups" value="Sitecore Admin" />
  </SourceClaims>
  <NewClaims>
    <Claim1 type="http://www.sitecore.net/identity/claims/isAdmin" value="true" />
  </NewClaims>
</ClaimsTransformation3>

The above transformation will give anyone in the Sitecore Admin group in Okta admin access to Sitecore. We can also leverage these transformations to map a group directly to a role.

<ClaimsTransformation3 type="Sitecore.Plugin.IdentityProviders.DefaultClaimsTransformation, Sitecore.Plugin.IdentityProviders">
  <SourceClaims>
    <Claim1 type="groups" value="Sitecore Developer" />
  </SourceClaims>
  <NewClaims>
    <Claim1 type="role" value="sitecore\Developer" />
  </NewClaims>
</ClaimsTransformation3>

Compile the solution, and deploy the things to IdentityServer. Once deployed, we can restart IdentityServer and navigate to our admin panel. If everything is working, then we should see an extra login button for Okta.

Login

As mentioned, most of the code is pretty straight forward. I have a full implementation of the IdentityServer code over on GitHub. Hopefully, it helps you get through the setup pretty quickly. 

One last thing: I am not a fan of having to update configuration files to do role mappings, as this requires deployments or restarts. In a previous post, I blogged about how I integrated the role mapping into Sitecore. That post was based on a pre-IdentityServer instance. Stay tuned for another post on how to bring group-to-role mapping into the Sitecore Content Editor even when IdentityServer is in the works.

UPDATE: Group-to-role mapping (the right way)

Happy Sitecore'ing!!!