0

I am working on upgrading a project from sitecore 8.1 to 9.3. I have added a new identity provider - AzureB2C to login into sites. It is using OpenIdConnect. I am able to navigate to the login page and get authenticated as well, but upon returning, I get blank error page -

enter image description here

It is redirecting to the redirectUrl configured and sends code, token and state as well. But after that this issue occurs. Below are the code and configs -

Config 1

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <initialize>
        <processor type="Project.SC.Feature.Login.Models.Routes.RegisterAuthRoutes, Project.SC.Feature.Login"
                   patch:before="processor[@type='Sitecore.Mvc.Pipelines.Loader.InitializeRoutes, Sitecore.Mvc']" resolve="true" />
      </initialize>
      <owin.identityProviders>
        <!--This is the custom processor that gets executed when azure AD posts the token to Sitecore-->
        <processor type="Project.SC.Feature.Login.Providers.AzureAdB2CIdentityProviderProcessor, Project.SC.Feature.Login" resolve="true" />
      </owin.identityProviders>
    </pipelines>
    <services>
     <!-- <configurator type= "Feature.AzureAdB2C.Models.AuthenticationConfigurator, Feature.AzureAdB2C"/>-->
      <register serviceType="Sitecore.Abstractions.BaseAuthenticationManager, Sitecore.Kernel"
                implementationType="Sitecore.Owin.Authentication.Security.AuthenticationManager, Sitecore.Owin.Authentication"
                lifetime="Singleton" />
      <register serviceType="Sitecore.Abstractions.BaseTicketManager, Sitecore.Kernel"
                implementationType="Sitecore.Owin.Authentication.Security.TicketManager, Sitecore.Owin.Authentication"
                lifetime="Singleton" />
      <register serviceType="Sitecore.Abstractions.BasePreviewManager, Sitecore.Kernel"
                implementationType="Sitecore.Owin.Authentication.Publishing.PreviewManager, Sitecore.Owin.Authentication"
                lifetime="Singleton" />
    </services>
    <federatedAuthentication type="Sitecore.Owin.Authentication.Configuration.FederatedAuthenticationConfiguration, Sitecore.Owin.Authentication">
      <!--Provider mappings to sites-->
      <identityProvidersPerSites hint="list:AddIdentityProvidersPerSites">
      </identityProvidersPerSites>

      <!--Definitions of providers-->
      <identityProviders hint="list:AddIdentityProvider">
        <identityProvider id="AzureAdB2C" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
          <param desc="name">AzureAdB2C</param>
          <param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
          <caption>AzureAdB2C</caption>
          <domain>CustomDomainName</domain>
          <transformations hint="list:AddTransformation">
            <transformation name="nameClaimTransformation" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
              <sources hint="raw:AddSource">
                <claim name="http://schemas.microsoft.com/identity/claims/objectidentifier" />
              </sources>
              <targets hint="raw:AddTarget">
                <claim name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier" />
              </targets>
              <keepSource>true</keepSource>
            </transformation>
          </transformations>
        </identityProvider>
      </identityProviders>

      <!--List of all shared transformations-->
      <sharedTransformations>
      </sharedTransformations>

      <!--Property mappings initializer-->
      <propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
      </propertyInitializer>

    </federatedAuthentication>
  </sitecore>
</configuration>

Config 2

<configuration> 
<sitecore>
<federatedAuthentication>
  <identityProvidersPerSites>
    <mapEntry name="portal" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication" resolve="true">
      <sites hint="list">
        <site>portal</site>
      </sites>
      <identityProviders hint="list:AddIdentityProvider">
        <identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='AzureAdB2C']"/>
      </identityProviders>
      <externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication" resolve="true">
        <param desc="isPersistentUser">false</param>
      </externalUserBuilder>
    </mapEntry>
  </identityProvidersPerSites>
</federatedAuthentication>
  </sitecore>
</configuration>

IdentityProviderProcessor -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Project.SC.Feature.Login.Configuration;
using Project.SC.Feature.Login.Extensions;
using Project.SC.Feature.Login.Models;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Notifications;
using Microsoft.Owin.Security.OpenIdConnect;
using Owin;
using Sitecore.Abstractions;
using Sitecore.Configuration;
using Sitecore.Diagnostics;
using Sitecore.Owin.Authentication.Configuration;
using Sitecore.Owin.Authentication.Extensions;
using Sitecore.Owin.Authentication.Pipelines.IdentityProviders;
using Sitecore.Owin.Authentication.Services;
using Sitecore.Text;
using Sitecore.Web;
using OpenIdConnectMessage = Microsoft.IdentityModel.Protocols.OpenIdConnect.OpenIdConnectMessage;

namespace Project.SC.Feature.Login.Providers
{
    [CLSCompliant(false)]
    public class AzureAdB2CIdentityProviderProcessor : IdentityProvidersProcessor
    {

        protected override string IdentityProviderName => "AzureAdB2C";

        public AzureAdB2CIdentityProviderProcessor(
            FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, ICookieManager cookieManager,
            BaseSettings settings) : base(federatedAuthenticationConfiguration, cookieManager, settings)
        {
            
        }

        protected override void ProcessCore(IdentityProvidersArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));
            Log.Info("Inside AzureAdB2CIdentityProviderProcessor -> processcore ", typeof(AzureAdB2CIdentityProviderProcessor));
            List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
            IEnumerable<OpenIdConnectSiteInfo> sites =
                siteInfoList.Select(s => new OpenIdConnectSiteInfo(s)).Where(s => s.UsesOpenIdConnect);

            foreach (OpenIdConnectSiteInfo site in sites)
            {
                Log.Info("Inside foreach site in sites AzureAdB2CIdentityProviderProcessor", typeof(AzureAdB2CIdentityProviderProcessor));

                // NOTE [ILs] SXA allows adding multiple hostnames to be matched seperated by |
                foreach (string hostname in site.HostName.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    Log.Info("Inside foreach hostname in site.hostname AzureAdB2CIdentityProviderProcessor", typeof(AzureAdB2CIdentityProviderProcessor));

                    // args.App.UseOpenIdConnectAuthentication(CreateOptionsFromSiteInfo(site));
                    args.App.MapWhen(
                       ctx => ctx.MapDomain(hostname) && ctx.MapFolder(site.VirtualFolder),
                       app =>
                       {
                           CookieAuthentication.ConfigureCookieAuthentication(app);
                           app.UseOpenIdConnectAuthentication(CreateOptionsFromSiteInfo(site));
                       });

                    Log.Info("After MapWhen AzureAdB2CIdentityProviderProcessor", typeof(AzureAdB2CIdentityProviderProcessor));

                }
            }
        }

        private OpenIdConnectAuthenticationOptions CreateOptionsFromSiteInfo(OpenIdConnectSiteInfo site)
        {
            OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions();
            var identityProvider = GetIdentityProvider();
            Log.Info("Start AzureAdB2CIdentityProviderProcessor -> CreateOptionsFromSiteInfo ", typeof(AzureAdB2CIdentityProviderProcessor));

            // For each policy, give OWIN the policy-specific metadata address, and
            // set the authentication type to the id of the policy
            options.MetadataAddress = site.Authority;
            options.AuthenticationType = GetAuthenticationType();
            options.RedirectUri = site.RedirectUri;
            options.PostLogoutRedirectUri = site.PostlogoutRedirectUri;
            options.TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = site.NameClaimType,
                SaveSigninToken = true
            };

            options.Notifications = new OpenIdConnectAuthenticationNotifications()
            {
                AuthenticationFailed = context => HandleOpenIdConnectAuthenticationFailed(context, site),
                RedirectToIdentityProvider = context => HandleOpenIdConnectRedirectToIdentityProvider(context, site),
                //SecurityTokenValidated = notification =>
                //{
                //    notification.AuthenticationTicket.Identity.AddClaim(new Claim("idp", "azureadb2c"));

                //    // transform all claims
                //    ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
                //    notification.AuthenticationTicket.Identity.ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));

                //    return Task.CompletedTask;
                //}
            };

            // These are standard OpenID Connect parameters, with values pulled from web.config
            options.ClientId = site.ClientId;
            //http://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
            options.Scope = site.Scope;
            //http://openid.net/specs/openid-connect-core-1_0.html#Authentication
            options.ResponseType = OpenIdConnectResponseTypes.CodeIdToken;

            Log.Info("End AzureAdB2CIdentityProviderProcessor -> CreateOptionsFromSiteInfo", typeof(AzureAdB2CIdentityProviderProcessor));

            return options;
        }

        /*
         *  On each call to Azure AD B2C, check if a policy (e.g. the profile edit or password reset policy) has been specified in the OWIN context.
         *  If so, use that policy when making the call. Also, don't request a code (since it won't be needed).
         */
        private static Task HandleOpenIdConnectRedirectToIdentityProvider(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification, OpenIdConnectSiteInfo site)
        {
            Log.Info("Start AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectRedirectToIdentityProvider", typeof(AzureAdB2CIdentityProviderProcessor));

            string policy = notification.OwinContext.Get<string>("Policy");

            if (!string.IsNullOrEmpty(policy) && !policy.Equals(site.SignInPolicyId))
            {
                notification.ProtocolMessage.Scope = OpenIdConnectScopes.OpenId;
                notification.ProtocolMessage.ResponseType = OpenIdConnectResponseTypes.IdToken;
                notification.ProtocolMessage.IssuerAddress = notification.ProtocolMessage.IssuerAddress.Replace(site.SignInPolicyId, policy);
            }

            try
            {
                AddLanguageQueryString(notification);
            }
            catch (Exception ex)
            {
                Log.Error(ex.Message, ex, typeof(OpenIdConnectAuthentication));
            }

            Log.Info("End AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectRedirectToIdentityProvider", typeof(AzureAdB2CIdentityProviderProcessor));

            return Task.FromResult(0);
        }

        private static void AddLanguageQueryString(RedirectToIdentityProviderNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
        {
            Log.Info("Start AzureAdB2CIdentityProviderProcessor -> AddLanguageQueryString", typeof(AzureAdB2CIdentityProviderProcessor));

            string portalUrlCookie = "Project_portal";
            string langPrefCookieFormat = "Project_{0}#lang";
            string language = string.Empty;
            if (notification.Request.Cookies[portalUrlCookie] != null)
            {
                string portalUrl = notification.Request.Cookies[portalUrlCookie];
                int pos = portalUrl.LastIndexOf("/") + 1;
                string locale = portalUrl.Substring(pos, portalUrl.Length - pos);
                language = locale.Split('-')?[0];
            }
            string refererUrl = notification.Request.Headers["referer"];
            string country = refererUrl.Split('/')[3];
            string langPrefCookie = string.Format(langPrefCookieFormat, country);
            if (notification.Request.Cookies[langPrefCookie] != null)
            {
                string prefLang = notification.Request.Cookies[langPrefCookie].Split('-')[0];
                if (!string.IsNullOrEmpty(prefLang))
                {
                    language = prefLang;
                }
                else
                {
                    language = refererUrl.Split('/')[4].Split('-')[0];
                }
            }

            if (!string.IsNullOrEmpty(language))
                notification.ProtocolMessage.Parameters.Add("ui_locales", language);
            Log.Info("End AzureAdB2CIdentityProviderProcessor -> AddLanguageQueryString", typeof(AzureAdB2CIdentityProviderProcessor));

        }
        private static Task HandleOpenIdConnectAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context, OpenIdConnectSiteInfo site)
        {
            Log.Info("Start AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectAuthenticationFailed", typeof(AzureAdB2CIdentityProviderProcessor));

            if (context.Exception.Message.Contains("IDX21323"))
            {
                context.HandleResponse();
                context.OwinContext.Authentication.Challenge();
            }
            else
            {
                context.HandleResponse();
                Log.Fatal(context.Exception.Message, context.Exception, typeof(OpenIdConnectAuthentication));
                UrlString errorUrl = new UrlString(site.ErrorUri);
                errorUrl.Add("message", context.Exception.Message);
                context.Response.Redirect(errorUrl.ToString());
            }
            Log.Info("End AzureAdB2CIdentityProviderProcessor -> HandleOpenIdConnectAuthenticationFailed", typeof(AzureAdB2CIdentityProviderProcessor));

            return Task.FromResult(0);
        }
    }
}

Update 1 In the logs I am getting below error -

ERROR Unable to find "idp" claim in the identity. Make sure that "Sitecore.Owin.Authentication.Services.SetIdpClaimTransform" or analogue is used in claim transformations of all identity providers.

Tried adding below transformation in config, but still no luck.

 <transformation name="Idp Claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication" />
5
  • Try restarting your CD instance and check your redirect url Sep 7 '21 at 13:12
  • @MaheshRaghupathi Thank you for your comment. Right now I am working on local, so cm and cd are same only. Restarting now.
    – NewbieHere
    Sep 7 '21 at 13:19
  • @MaheshRaghupathi still getting the same error
    – NewbieHere
    Sep 7 '21 at 13:50
  • In the posted code the call to apply the transformations (ApplyClaimsTransformations) is commented out, you still get the error in log if you uncomment that?
    – Jeroen
    Sep 8 '21 at 14:11
  • @Jeroen thank you for your comment. yes, i have uncommented it. still getting the same error.
    – NewbieHere
    Sep 8 '21 at 14:17
1

I believe your approach is wrong. I once tried something similar, and using args.App.MapWhen directly is not supported as per Sitecore support, see more here. This is how I have seen this work:

  1. Remove the args.App.MapWhen and use same UseOpenIdConnectAuthentication for each site

  2. Inside the RedirectToIdentityProvider set the correct OIDC options for the site, an example of this can be found in this answer. In case there are any settings which are not available here then you are probably in an area which is not supported by Sitecore Federated Authentication.

2
  • Hi @jeroen Thank you for your answer. I changed the code as per your answer but still getting the same error.
    – NewbieHere
    Sep 10 '21 at 10:07
  • You able to post your updated code and config?
    – Jeroen
    Sep 10 '21 at 15:07
0

Check the answer to below stackexchange question for working code - Federated Authentication with Azure AD - Sitecore 9.3 - .Aspnet .Cookies not getting created

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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