1

I've been following this guide to enable SSO on our CM/CD instances to create virtual users via AzureAD. From what I can see it all looks fine but the redirect is not firing when a page is set to deny anonymous users. It's not triggering to redirect to https://login.microsoftonline.com/ in order to obtain the cookie.

My config looks like this:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/" xmlns:set="http://www.sitecore.net/xmlconfig/set/" xmlns:security="http://www.sitecore.net/xmlconfig/security/">
<sitecore role:require="Standalone or ContentManagement or ContentDelivery">
    <federatedAuthentication type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication">
        <identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
            <mapEntry name="all sites" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication">
                <sites hint="list">
                    <site>Ljmu</site>
                    <site>website</site>
                </sites>
                <identityProviders hint="list:AddIdentityProvider">
                    <identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='AzureAd']" />
                </identityProviders>
                <externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication" resolve="true">
                    <IsPersistentUser>false</IsPersistentUser>
                </externalUserBuilder>
            </mapEntry>
            <mapEntry name="admin site" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication">
                <sites hint="list">
                    <site>admin</site>
                </sites>
                <identityProviders hint="list:AddIdentityProvider">
                    <identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='SitecoreIdentityServer']" />
                </identityProviders>
            </mapEntry>
        </identityProvidersPerSites>
        <identityProviders hint="list:AddIdentityProvider">
            <identityProvider id="AzureAd" 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 Microsoft Azure</caption>
                <icon>/sitecore/shell/themes/standard/Custom/24x24/msazure.png</icon>
                <domain>sitecore</domain>
                <enabled>true</enabled>
                <transformations hint="list:AddTransformation">
                    <transformation type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
                        <sources hint="raw:AddSource">
                            <claim name="groups" value="21aebc04-8728-4c25-9b11-8f78cef5fedf" />
                        </sources>
                        <targets hint="raw:AddTarget">
                            <claim name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" value="extranet\ljmu staff" />
                        </targets>
                        <keepSource>true</keepSource>
                    </transformation>
                    <transformation type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
                        <sources hint="raw:AddSource">
                            <claim name="groups" value="ac9c149d-5c54-4ee8-ae8d-cc7eaa55e7fa" />
                        </sources>
                        <targets hint="raw:AddTarget">
                            <claim name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" value="extranet\ljmu student" />
                        </targets>
                        <keepSource>true</keepSource>
                    </transformation>
                </transformations>
            </identityProvider>
            <identityProvider id="SitecoreIdentityServer" type="Sitecore.Owin.Authentication.IdentityServer.IdentityServerProvider, Sitecore.Owin.Authentication.IdentityServer" resolve="true">
                <transformations hint="list:AddTransformation">
                    <transformation name="admin-ify client credentials users" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
                        <sources hint="raw:AddSource">
                            <claim name="client_id" value="LJMU_Automation" />
                        </sources>
                        <targets hint="raw:AddTarget">
                            <claim name="name" value="sitecore\SitecoreCLIClientCredentials" />
                            <claim name="http://www.sitecore.net/identity/claims/isAdmin" value="true" />
                        </targets>
                        <keepSource>true</keepSource>
                    </transformation>
                </transformations>
            </identityProvider>
        </identityProviders>
        <sharedTransformations hint="list:AddSharedClaimsTransformation">
            <transformation name="set idp claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication" />
            <transformation name="sanitize claims" type="Sitecore.Owin.Authentication.Services.RemoveClaims, Sitecore.Owin.Authentication">
                <claimTypes hint="list">
                </claimTypes>
            </transformation>
            <transformation name="set id_token claim" type="Sitecore.Owin.Authentication.Services.SaveIdTokenInClaim, Sitecore.Owin.Authentication" />
        </sharedTransformations>
        <propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
            <maps hint="list">
                <map name="set Email" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication" resolve="true">
                    <data hint="raw:AddData">
                        <source name="email" />
                        <target name="Email" />
                    </data>
                </map>
                <map name="set FullName" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication" resolve="true">
                    <data hint="raw:AddData">
                        <source name="name" />
                        <target name="FullName" />
                    </data>
                </map>
            </maps>
        </propertyInitializer>
    </federatedAuthentication>
    <!-- Force redirect to Azure login page
<sites>
  <site name="shell" set:loginPage="$(loginPath)shell/SitecoreIdentityServer/IdS4-AzureAd" />
  <site name="admin" set:loginPage="$(loginPath)admin/SitecoreIdentityServer" />
</sites>-->
    <pipelines>
        <owin.identityProviders>
            <processor type="Ljmu.Web.Infrastructure.Sc.Pipelines.AzureADFederatedAuthentication.AzureADFederatedAuthenticationProcessor, Ljmu.Web.Infrastructure" resolve="true" />
        </owin.identityProviders>
    </pipelines>
</sitecore>

And here's my corresponding IdentityProviderProcessor...

namespace Ljmu.Web.Infrastructure.Sc.Pipelines.AzureADFederatedAuthentication
{
     public class AzureADFederatedAuthenticationProcessor : IdentityProvidersProcessor
     {
         private FederatedAuthenticationConfiguration _federatedAuthenticationConfiguration;
         private ICookieManager _cookieManager;
         private BaseSettings _baseSettings;

         public AzureADFederatedAuthenticationProcessor(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, ICookieManager cookieManager, BaseSettings settings) : base(federatedAuthenticationConfiguration, cookieManager, settings)
    {
        _federatedAuthenticationConfiguration = federatedAuthenticationConfiguration;
        _cookieManager = cookieManager;
        _baseSettings = settings;
    }

    protected override string IdentityProviderName => "AzureAd";

    /// <summary>
    /// 
    /// </summary>
    /// <param name="args"></param>
    protected override void ProcessCore([NotNull] IdentityProvidersArgs args)
    {
        Assert.ArgumentNotNull(args, nameof(args));

        var identityProvider = this.GetIdentityProvider();
        var authenticationType = this.GetAuthenticationType();
        var saveSigninToken = identityProvider.TriggerExternalSignOut;

        var targetHostName = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
        string aadInstance = "https://login.microsoftonline.com/{0}/";
        string tenant = Settings.GetSetting("AzureAD.FederatedAuthentication.TenantId");
        string clientId = Settings.GetSetting("AzureAD.FederatedAuthentication.ClientId");
        string clientSecret = Settings.GetSetting("AzureAD.FederatedAuthentication.ClientSecret");
        string postLogoutRedirectURI = $"{targetHostName}{Settings.GetSetting("AzureAD.FederatedAuthentication.PostLogoutRedirectURI")}";
        string redirectURI = $"{targetHostName}{Settings.GetSetting("AzureAD.FederatedAuthentication.RedirectURI")}";
        string scope = Settings.GetSetting("AzureAD.FederatedAuthentication.Scope");

        string authority = string.Format(CultureInfo.InvariantCulture, aadInstance, tenant);

        //Set the authentication type to use cookies
        args.App.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        });


        args.App.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            Caption = identityProvider.Caption,
            AuthenticationType = authenticationType,
            AuthenticationMode = AuthenticationMode.Passive,
            ClientId = clientId,
            ClientSecret = clientSecret,
            Authority = authority,
            PostLogoutRedirectUri = postLogoutRedirectURI,
            RedirectUri = redirectURI,
            ResponseType = OpenIdConnectResponseType.CodeIdToken,
            UseTokenLifetime = false,
            CookieManager = this.CookieManager,
            Scope = scope,
            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                SecurityTokenValidated = notification =>
                {
                    var identity = notification.AuthenticationTicket.Identity;
                    foreach (var claimTransformationService in identityProvider.Transformations)
                    {
                        claimTransformationService.Transform(identity,
                            new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));

                    }

                    notification.AuthenticationTicket = new AuthenticationTicket(identity, notification.AuthenticationTicket.Properties);

                    return Task.FromResult(0);
                },
                RedirectToIdentityProvider = message =>
                {
                    // format redirect URI so Sitecore cleans up after itself
                    var revokeProperties = message.OwinContext.Authentication.AuthenticationResponseRevoke?.Properties?.Dictionary;
                    if (revokeProperties != null && revokeProperties.ContainsKey("nonce"))
                    {
                        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"];

                        // for single sign-out, Sitecore expects the URI used below with the nonce in the query string
                        // this URI was found in Sitecore.Owin.Authentication.Pipelines.Initialize.HandlePostLogoutUrl
                        message.ProtocolMessage.PostLogoutRedirectUri = $"{host}/identity/postexternallogout?ReturnUrl={path}&nonce={nonce}";
                    }

                    return Task.FromResult(0);
                }

            },
            TokenValidationParameters =
           {
               SaveSigninToken = saveSigninToken
           }
        });
    }
}

Are there any additional config settings I've missed?

6
  • you mean to say the redirect link is not getting created correctly containing client id?
    – ckhanna
    Commented May 21 at 14:25
  • @ckhanna I mean, if I set extranet/anonymous user to deny on a page, the redirect itself does not fire. It just goes to the NoAccessUrl defined in sitecore settings config. I'm expecting it to go to the microsoft sign in page first.
    – Sean T
    Commented May 21 at 14:33
  • have you implemented the link through GetSignInUrlInfoPipeline pipeline. This returns the link in this format auth.nxp.norstella.com/…. Is the link getting created using this format?
    – ckhanna
    Commented May 21 at 14:55
  • @ckhanna Ahh no, I've not implemented this - I assume there's an event / pipeline I can hook into when a page is denied that allows me to redirect to that subsequent url? Rather than have the user be forced to click a link
    – Sean T
    Commented May 21 at 15:18
  • ok, I think I have it - httprequest pipeline, check user's role has permission to read the context item and if not send them to that url generated by GetSignInUrlInfoPipeline(). I'll give that a go tomorrow
    – Sean T
    Commented May 21 at 15:26

1 Answer 1

3

In order to achieve this, you will need to get the link through GetSignInUrlInfoPipeline pipeline and read the link in the custom processor to redirect to that link before the ExecuteRequest pipeline as below.

using Sitecore;
using Sitecore.Abstractions;
using Sitecore.Pipelines.GetSignInUrlInfo;
using Sitecore.Pipelines.HttpRequest;
using System.Linq;

public class NoAccessRedirect : HttpRequestProcessor
    {
        private readonly BaseCorePipelineManager _baseCorePipelineManager;
        public NoAccessRedirect(BaseCorePipelineManager baseCorePipelineManager)
        {
            _baseCorePipelineManager = baseCorePipelineManager;
        }
        public override void Process(HttpRequestArgs args)
        {
            if (args.PermissionDenied)
            {
                var signArgs = new GetSignInUrlInfoArgs(site: Context.Site.Name, returnUrl: "/");
                GetSignInUrlInfoPipeline.Run(_baseCorePipelineManager, signArgs);
                var redirectUrl = signArgs.Result?.FirstOrDefault()?.Href;
                if (!string.IsNullOrEmpty(redirectUrl))
                {
                    args.HttpContext.Response.Redirect(redirectUrl, true);
                    args.AbortPipeline();
                }
            }
        }
    }

Inside the pipeline you can read the permission to the page using args.PermissionDenied attribute from HttpRequestArgs argument.

Note: You will need to patch your processor before ExecuteRequest pipeline, since this processor is responsible for redirecting to noaccess page url.

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
 <sitecore>
   <pipelines>
     <httpRequestBegin>
      <processor type="SitecoreCustomExtensions.Pipelines.NoAccessRedirect,SitecoreCustomExtensions"  patch:before="processor[@type='Sitecore.Pipelines.HttpRequest.ExecuteRequest, Sitecore.Kernel']" resolve="true" >
      </processor>
     </httpRequestBegin>
    </pipelines>
  </sitecore>
</configuration>

Hope this helps!!!

4
  • Thanks, I've implemented this and it's now firing. just having an issue with it complaining that the redirect must be a POST rather than a GET.
    – Sean T
    Commented May 23 at 11:02
  • Not sure but I think this can be configured in the identity provider. I worked on OKTA, there it uses GET url for redirecting to login page. Not sure about Azure AD.
    – ckhanna
    Commented May 23 at 12:05
  • I've found a solution, I have your pipeline redirect to a login page hosted within sitecore instead of azureAD, the login page has a component that runs the GetSignInUrlInfoPipeline(), generating the azureAD link and rendering it as a form post destination. Some js then fires it automatically. Thanks for the help :)
    – Sean T
    Commented May 23 at 15:39
  • Glad it helped. Great :)
    – ckhanna
    Commented May 23 at 15:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.