0

We are using Sitecore version 9.3 for our solution.

For our CD instance (the public facing website), we need to use Azure ADB2C with OpenIdConnect for authentication. And for our Sitecore CM instance, we have to use Sitecore Identity only.

Below is all the code that we have added to make this work. The authentication with the public website works fine but when we try to login to our Sitecore CM instance, we are getting an error below:

Unsuccessful login with external provider. 

Please help us in rectifying this issue.

Processor

 public class AzureAdB2CIdentityProvider : IdentityProvidersProcessor
{
    private readonly string Idp = "idp";
    protected override string IdentityProviderName => "AzureAdB2C";

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

    protected override void ProcessCore(IdentityProvidersArgs args)
    {
        Assert.ArgumentNotNull(args, nameof(args));
      
        List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
        IEnumerable<OpenIdConnectSiteInfo> sites = siteInfoList.Select(s => new OpenIdConnectSiteInfo(s))
                                                   .Where(s => s.UsesOpenIdConnect);

        IEnumerable<WsFederatedSiteInfo> wsFederatedSites = siteInfoList.Select(s => new WsFederatedSiteInfo(s))
                                                 .Where(s => s.IsFederated);


        CookieAuthentication.ConfigureCookieAuthentication(args.App);
        foreach (OpenIdConnectSiteInfo site in sites)
        {             
            // NOTE [ILs] SXA allows adding multiple hostnames to be matched seperated by |
            foreach (string hostname in site.HostName.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
            {
                  args.App.UseOpenIdConnectAuthentication(CreateOptionsFromOpenIdSiteInfo(site));
            }
        }

        foreach (WsFederatedSiteInfo site in wsFederatedSites)
        {
            // NOTE [ILs] SXA allows adding multiple hostnames to be matched seperated by |
            foreach (string hostname in site.HostName.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
            {
                args.App.UseWsFederationAuthentication(CreateOptionsFromWsSiteInfo(site));
            }
        }
    }

    private WsFederationAuthenticationOptions CreateOptionsFromWsSiteInfo(WsFederatedSiteInfo site)
    {
        return new WsFederationAuthenticationOptions
        {
            UseTokenLifetime = true,
            MetadataAddress = site.MetadataAddress,
            Wtrealm = "https://" + site.Realm,
            Wreply = site.ReplyUrl,
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = site.NameClaimType,
                ValidAudiences = new List<string> { "https://" + site.Realm.WithPostfix('/') }
            },
            Notifications = new WsFederationAuthenticationNotifications()
            {
                SecurityTokenValidated = notification => HandleSecurityTokenValidatedWsFederated(notification, GetIdentityProvider())
            }
        };
    }

    private Task HandleSecurityTokenValidatedWsFederated(SecurityTokenValidatedNotification<WsFederationMessage, WsFederationAuthenticationOptions> notification, IdentityProvider identityProvider)
    {
        notification.AuthenticationTicket.Identity.AddClaim(new Claim(Idp, IdentityProviderName));
        // transform all claims
        ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
        notification.AuthenticationTicket.Identity
                    .ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration,
                                                                          identityProvider));

        return Task.FromResult(0);
    }

    private OpenIdConnectAuthenticationOptions CreateOptionsFromOpenIdSiteInfo(OpenIdConnectSiteInfo site)
    {
        return new OpenIdConnectAuthenticationOptions()
        {
            MetadataAddress = site.Authority,
            RedirectUri = site.RedirectUri,
            PostLogoutRedirectUri = site.PostlogoutRedirectUri,
            ClientId = site.ClientId,
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = site.NameClaimType,
                SaveSigninToken = true,
            },
            Notifications = new OpenIdConnectAuthenticationNotifications()
            {
                AuthenticationFailed = context => HandleOpenIdConnectAuthenticationFailed(context, site),
                RedirectToIdentityProvider = context => HandleOpenIdConnectRedirectToIdentityProvider(context, site),
                SecurityTokenValidated = notification => HandleSecurityTokenValidated(notification, GetIdentityProvider()),
            }
        };         
    }

    private Task HandleSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification, IdentityProvider identityProvider)
    {

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

        return Task.FromResult(0);
    }

    /*
     *  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)
    {
        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);
        }
       

        return Task.FromResult(0);
    }

    
    private static Task HandleOpenIdConnectAuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> context, OpenIdConnectSiteInfo site)
    {
        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());
        }
        return Task.FromResult(0);
    }
}

Config

   <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <owin.identityProviders>
        <!--This is the custom processor that gets executed when azure AD posts the token to Sitecore-->
        <processor type="OurProject.SC.Feature.Login.Providers.AzureAdB2CIdentityProvider, OurProject.SC.Feature.Login" resolve="true" id="AzureADB2C" />
      </owin.identityProviders>
    </pipelines>
    <services>
      <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>
        <mapEntry name="OurProject" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication" resolve="true">
          <sites hint="list">
            <site>OurProject_Site_Name</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>

      <!--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>OurProject_CustomDomain</domain>
          <transformations hint="list:AddTransformation">
            <transformation name="Idp Claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication">
            </transformation>
            <transformation name="Name Identifier Claim" 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.microsoft.com/ws/2008/06/identity/claims/role" value="OurProject\Anonymous"> </claim>
                <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 hint="list:AddSharedClaimsTransformation">
      </sharedTransformations>

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

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

Update 1

Upon further analysis, I found out that the issue is with this statement -

app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

This is getting called inside -

  CookieAuthentication.ConfigureCookieAuthentication(args.App);

When I comment it out, I am able to login to sitecore but login to the public facing site breaks.

Update 2 After commenting the code as mentioned in Update 1, I can't see ".Aspnet .Cookies" getting created. I only see ".Aspnet .ExternalCookies". How to get ".Aspnet .Cookies" ?

Update 3 enter image description here

Login Controller -

     [Authorize]
        public ActionResult Login()
        {
// other logic to get identity information from .AspNet.Cookies and create virtual user, etc
but on removing app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType); this Action is not getting hit
         }
4
  • Can you clarify if you have separate Sitecore CM and Sitecore CD instances?
    – Julius A
    Oct 6 at 12:44
  • @JuliusA Yes, we have separate CM and CD instances. So, earlier I added the config file to CD only. But now, we have a requirement to create a site on CM IIS Server that will read the files from CM only. This site will be authenticated using AzureADB2C.
    – NewbieHere
    Oct 6 at 12:46
  • Is the new site you are creating within Sitecore (multi-site) or separate IIS website deployed on IIS as your CM instance? I think you need to sort out the architecture of your new website. If it is public facing, I will not expect it to run within same environment as CM (which is not public facing)
    – Julius A
    Oct 6 at 12:50
  • @JuliusA We are upgrading the existing sites from 8.1 to 9.3. So, cannot change the architecture that exists. It is like 2 sites on IIS are pointing to the same directory. In sitedefinitions, we have added entry for this new site.
    – NewbieHere
    Oct 6 at 12:52
1
 Below code worked me - (able to login to public site without breaking sitecore login)

 public class AzureAdB2CWsFedIdentityProvider : IdentityProvidersProcessor
        {
            private readonly string Idp = "idp";
            protected override string IdentityProviderName => "AzureAdB2C";
            private readonly string AuthenticationType = "ExternalCookie";
            private readonly string CookieName = ".AspNet.Cookies";
    
            public AzureAdB2CWsFedIdentityProvider(
                FederatedAuthenticationConfiguration federatedAuthenticationConfiguration, 
                ICookieManager cookieManager,
                BaseSettings settings) : 
                base(federatedAuthenticationConfiguration, cookieManager, settings)
            {
                
            }

        protected override void ProcessCore(IdentityProvidersArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));
          
            List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
            IEnumerable<WsFederatedSiteInfo> wsFederatedSites = siteInfoList.Select(s => new WsFederatedSiteInfo(s))
                                                                .Where(s => s.IsFederated);
         
            foreach (WsFederatedSiteInfo site in wsFederatedSites)
            {
                // NOTE [ILs] SXA allows adding multiple hostnames to be matched seperated by |
                foreach (string hostname in site.HostName.Split(new[] { '|' }, StringSplitOptions.RemoveEmptyEntries))
                {
                    args.App.UseCookieAuthentication(
                          new CookieAuthenticationOptions()
                          {
                              AuthenticationType = AuthenticationType,\\updated code
                              CookieName = CookieName, \\updated code
                          });
                    args.App.UseWsFederationAuthentication(CreateOptionsFromWsSiteInfo(site));
                }
            }
        }

        private WsFederationAuthenticationOptions CreateOptionsFromWsSiteInfo(WsFederatedSiteInfo site)
        {
            return new WsFederationAuthenticationOptions
            {
                UseTokenLifetime = true,
                MetadataAddress = site.MetadataAddress,
                Wtrealm = "https://" + site.Realm,
                Wreply = site.ReplyUrl,
                TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = site.NameClaimType,
                    ValidAudiences = new List<string> { "https://" + site.Realm.WithPostfix('/') }
                },
                Notifications = new WsFederationAuthenticationNotifications()
                {
                    SecurityTokenValidated = notification => HandleSecurityTokenValidated(notification, GetIdentityProvider())
                }
            };
        }

        private Task HandleSecurityTokenValidated(SecurityTokenValidatedNotification<WsFederationMessage, WsFederationAuthenticationOptions> notification, IdentityProvider identityProvider)
        {
            notification.AuthenticationTicket.Identity.AddClaim(new Claim(Idp, IdentityProviderName));
            // transform all claims
            ClaimsIdentity identity = notification.AuthenticationTicket.Identity;
            notification.AuthenticationTicket.Identity
                        .ApplyClaimsTransformations(new TransformationContext(FederatedAuthenticationConfiguration,
                                                                              identityProvider));
            return Task.CompletedTask;
        }
    }

Basically I added -

args.App.UseCookieAuthentication(
                          new CookieAuthenticationOptions()
                          {
                              AuthenticationType = "ExternalCookie",
                              CookieName = ".AspNet.Cookies", 
                          });

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.