0

We have an ASP.NET webforms application with Sitecore 9.0.0 Initial release, and we want to use Azure B2C as an external identity provider for our external users. We created the new authentication provider following Sitecore documents.

Here is the Provider Processor:

 public class AzureAdB2CIdentityProviderProcessor : IdentityProvidersProcessor
    {
        public AzureAdB2CIdentityProviderProcessor(FederatedAuthenticationConfiguration federatedAuthenticationConfiguration)
        : base(federatedAuthenticationConfiguration)
        {

        }
        protected override string IdentityProviderName
        {
            get { return "AzureAD.B2C"; }
        }

        //Manage Policies


        private OpenIdConnectAuthenticationOptions CreateOptionsFromPolicy(string authenticationType)
        {
            var identityProvider = this.GetIdentityProvider();

            Log.Debug(String.Format("authenticationType: {0}", authenticationType));


            var options = new OpenIdConnectAuthenticationOptions
            {
                // For each policy, give OWIN the policy-specific metadata address, and
                // set the authentication type to the id of the policy
                AuthenticationMode = AuthenticationMode.Passive,
                AuthenticationType = authenticationType,
                MetadataAddress = String.Format(Globals.WellKnownMetadata, Globals.Tenant, Globals.SignUpSignInPolicyId),
                Caption = identityProvider.Caption,
                // These are standard OpenID Connect parameters, with values pulled from web.config
                ClientId = Globals.ClientId,
                PostLogoutRedirectUri = Globals.postLogoutRedirectURI,
                RedirectUri = Globals.RedirectUri,
                Scope = "openid",
                ResponseType = "id_token",
                UseTokenLifetime = false,

                Notifications = new OpenIdConnectAuthenticationNotifications
                {
                    AuthenticationFailed = notification =>
                    {
                        System.Diagnostics.Debug.WriteLine("AD-B2C\\AuthenticationFailed");
                        //To handle scenario where user logs in, but cancels consenting to rights to read directory profile
                        string appBaseUrl = notification.Request.Scheme + "://" + notification.Request.Host + notification.Request.PathBase;
                        notification.ProtocolMessage.RedirectUri = appBaseUrl + "/";
                        //this is where the magic happens
                        notification.HandleResponse();
                        notification.Response.Redirect(notification.ProtocolMessage.RedirectUri);
                        return Task.CompletedTask;
                        //return Task.FromResult(0);
                    },

                    SecurityTokenValidated = notification =>
                    {

                        var identity = notification.AuthenticationTicket.Identity;

                        //This is based from a solution by Sean Sartell to allow Sitecore to correctly log out
                        identity.AddClaim(new Claim("id_token", notification.ProtocolMessage.IdToken));

                        ////Apply claim transformations
                        //notification.AuthenticationTicket.Identity.ApplyClaimsTransformations(
                        //    new TransformationContext(FederatedAuthenticationConfiguration, identityProvider)
                        //);

                        foreach (var claimTransformationService in identityProvider.Transformations)
                        {
                            claimTransformationService.Transform(identity,
                                new TransformationContext(FederatedAuthenticationConfiguration, identityProvider));
                        }

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

                        return Task.CompletedTask;
                    },
                    RedirectToIdentityProvider = notification =>
                    {
                        //The following code is based from Sean Sartell solution for correct Sitecore logout redirection
                        var revokeProperties = notification.OwinContext.Authentication.AuthenticationResponseRevoke?.Properties?.Dictionary;

                        if (revokeProperties != null && revokeProperties.ContainsKey("nonce") && revokeProperties.ContainsKey("id_token"))
                        {
                            var uri = new Uri(notification.ProtocolMessage.PostLogoutRedirectUri);
                            var host = uri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
                            var path = $"/{uri.GetComponents(UriComponents.Path, UriFormat.Unescaped)}";
                            var nonce = revokeProperties["nonce"];

                            notification.ProtocolMessage.PostLogoutRedirectUri = $"{host}/identity/postexternallogout?ReturnUrl={path}&nonce={nonce}";

                            //This is not in Sean Sartell's solution, but it is needed for IdentityServer4 to
                            //redirect from the identity provider back to the Sitecore postexternallogout handler
                            notification.ProtocolMessage.IdTokenHint = revokeProperties["id_token"];
                        }

                        return Task.CompletedTask;
                    }
                },

                // This piece is optional - it is used for displaying the user's name in the navigation bar.
                TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                {
                    NameClaimType = "emails"
                }
            };

            return options;
        }


        protected override void ProcessCore(IdentityProvidersArgs args)
        {
            Assert.ArgumentNotNull(args, nameof(args));
            var authenticationType = this.GetAuthenticationType();
            args.App.UseOpenIdConnectAuthentication(CreateOptionsFromPolicy(authenticationType));
        }
    }

Here is the .config file related to the Authentication Provider:

<?xml version="1.0" encoding="utf-8"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/" xmlns:role="http://www.sitecore.net/xmlconfig/role/">
  <sitecore role:require="Standalone or ContentDelivery or ContentManagement">
    <settings>
      <!-- you need to replace below client id with your azure application ID that we preserved from step 5 of Azure AD configuration -->
      <setting name="B2C_ApplicationId" value="xxxxxxx-xxxxxxxx-xxxxxxx-xxxx" />
      <setting name="B2C_AADInstance" value="https://xxxxxxxx.b2clogin.com/tfp/{0}/{1}" />
      <!-- This url will be used when we don't use Owin and impliment our own way of authentication. -->

      <setting name="B2C_Tenant" value="xxxxxxxxxx.onmicrosoft.com" />
      <!-- your Sitecore instance Url-->
      <setting name="B2C_RedirectUrl" value="https://www.sample.com" />
      <!-- your Sitecore SignInUpPolicy-->
      <setting name="B2C_SignInUpPolicy" value="xxxxxxxx" />
      <setting name="B2C_PostLogoutRedirectURI" value="https://www.sample.com" />
    </settings>
    <pipelines>
      <owin.identityProviders>
        <!-- This is the custom processor that gets executed when azure AD posts the token to Sitecore -->
        <processor type="CustomProvider.Processors.AzureAdB2CIdentityProviderProcessor, CustomProvider" resolve="true" />
      </owin.identityProviders>
    </pipelines>
    <federatedAuthentication>
      <identityProviders hint="list:AddIdentityProvider">
        <identityProvider id="AzureAD.B2C" type="Sitecore.Owin.Authentication.Configuration.DefaultIdentityProvider, Sitecore.Owin.Authentication">
          <param desc="name">$(id)</param>
          <param desc="domainManager" type="Sitecore.Abstractions.BaseDomainManager" resolve="true" />
          <caption>Sign with Azure B2C</caption>
          <domain>xxxx</domain>
          <icon>signin__azure_adB2C</icon>
          <transformations hint="list:AddTransformation">
            <!-- you need to have and Idp Claim for this to work -->
            
            <!--<transformation name="Idp Claim" ref="federatedAuthentication/sharedTransformations/setIdpClaim" />-->
            <transformation name="Idp Claim" type="Sitecore.Owin.Authentication.Services.SetIdpClaimTransform, Sitecore.Owin.Authentication" />

            <transformation name="map role to idp" type="Sitecore.Owin.Authentication.Services.DefaultTransformation, Sitecore.Owin.Authentication">
              <sources hint="raw:AddSource">
                    <claim name="memberRoles" value="value" />
              </sources>
              <targets hint="raw:AddTarget">
                <claim name="http://schemas.microsoft.com/ws/2008/06/identity/claims/role" value="xxxxx\some role" />
              </targets>
              <keepSource>true</keepSource>
            </transformation>
          </transformations>
        </identityProvider>
      </identityProviders>
      <!-- Property initializer assigns claim values to sitecore user properties -->
      <propertyInitializer type="Sitecore.Owin.Authentication.Services.PropertyInitializer, Sitecore.Owin.Authentication">
        <maps hint="list">
          <map name="FullName" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
            <data hint="raw:AddData">
              <!--claim name-->
              <source name="name" />
              <!--property name-->
              <target name="FullName" />
            </data>
          </map>
          <map name="Emails" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
            <data hint="raw:AddData">
              <!--claim name-->
              <source name="emails" />
              <!--property name-->
              <target name="Email" />
            </data>
          </map>
           <map name="Idp" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
            <data hint="raw:AddData">
              <!--claim name-->
              <source name="idp" />
              <!--property name-->
              <target name="Comment" />
            </data>
          </map>
          <map name="Name" type="Sitecore.Owin.Authentication.Services.DefaultClaimToPropertyMapper, Sitecore.Owin.Authentication">
            <data hint="raw:AddData">
              <!--claim name-->
              <source name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname" />
              <!--property name-->
              <target name="Name" />
            </data>
          </map>
          
          
        </maps>
      </propertyInitializer>
      <identityProvidersPerSites>
        <mapEntry name="b2c" type="Sitecore.Owin.Authentication.Collections.IdentityProvidersPerSitesMapEntry, Sitecore.Owin.Authentication">
          <sites hint="list">
            <site>website</site>
          </sites>
          <!-- Registered identity providers for above providers -->
          <identityProviders hint="list:AddIdentityProvider">
            <identityProvider ref="federatedAuthentication/identityProviders/identityProvider[@id='AzureAD.B2C']" />
          </identityProviders>

          <externalUserBuilder type="Sitecore.Owin.Authentication.Services.DefaultExternalUserBuilder, Sitecore.Owin.Authentication">
            <param desc="isPersistentUser">true</param>
          </externalUserBuilder>
        </mapEntry>
      </identityProvidersPerSites>
    </federatedAuthentication>
  </sitecore>
</configuration>

We generated the sign-in links with the following code:

var args = new GetSignInUrlInfoArgs("website", url);
GetSignInUrlInfoPipeline.Run(corePipelineManager, args);
args.Result.FirstOrDefault().Href

I attached the Href property to an asp.net button, so our website was able to redirect the request to the IDP, after a successful login in the Azure B2C the response is sent back to the website.

If we debug our code, the first time we can see that the user is authenticated, but if we click over an internal website link or refresh the website, the result is that data related to the user is not being persisted, so extranet\anonymous user is set again.

We checked the application cookies after a successful login, but the authentication cookie is not set (.aspnet.cookie). So user related data is not persisted.

What do you think could be the issue?

Your Answer

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

Browse other questions tagged or ask your own question.