2

We want to allow our endusers to login to our website using an Azure B2C AD account. I have only been able to find examples of how to add federated authentication for Sitecore accounts

I have added this configuration:

<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
<pipelines>
  <owin.initialize>
    <processor type="Feature.Authentication.RegisterOwinAuthPipeline, Feature.Authentication" 
               patch:before="processor[@type='Sitecore.Owin.Pipelines.Initialize.StageMarker, Sitecore.Owin' and @method='Authenticate']"/>
  </owin.initialize>
</pipelines>

Code for auth-route:

public virtual void Process(PipelineArgs args)
    {
        RouteTable.Routes.MapRoute("AuthRoute", "auth/sso/{action}", new
        {
            controller = "Sso"
        });

        SsoLogger.Info("RegisterAuthRoute - Registered route: auth/sso/{action}");

        RouteTable.Routes.MapRoute("LoginContentRoute", "api/content/{action}", new
        {
            controller = "Content"
        });

        SsoLogger.Info("LoginContentRoute - Registered route: api/login/{action}");
    }

And the pipeline that registers my Owin setup:

public class RegisterOwinAuthPipeline : InitializeProcessor
{
    private readonly string _ssoAuthenticationRootPath = SsoConfiguration.Routes.RootPath;
    private readonly string _aadInstance = SsoConfiguration.AadInstance;
    private readonly string _tenant = SsoConfiguration.Tenant;
    private readonly ISsoConfigurationRepository _ssoConfigurationRepository;

    public RegisterOwinAuthPipeline()
    {
        _ssoConfigurationRepository = ServiceLocator.ServiceProvider.GetService<ISsoConfigurationRepository>();
    }

    public override void Process(InitializeArgs args)
    {
        ConfigureAuth(args.App);
    }

    public void ConfigureAuth(IAppBuilder app)
    {
        app.Map(_ssoAuthenticationRootPath, ConfigureSsoAuthentication);
        SsoLogger.Info("Owin Startup configuration done. AadInstance=" + _aadInstance + " and Tenant=" + _tenant);
    }

    private void ConfigureSsoAuthentication(IAppBuilder app)
    {
        // After successful sign-in in SSO, the redirect back to this web site will use the cookie authentication middleware to contain the obtained id_token.
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            CookieName = SsoConfiguration.SsoCookieName,
            CookiePath = _ssoAuthenticationRootPath, // Scope cookies to the authentication root path in order to avoid having the cookie included in every normal request.
            TicketDataFormat = new TicketDataFormat(new MachineKeyProtector()) // In order to be able to decrypt the authentication ticket stored in the cookie later on, we use protection based on the machine key.
        });

        List<B2CConfig> ssoSettings = _ssoConfigurationRepository.GetAllSettings();

        foreach (var config in ssoSettings)
        {
            app.UseOpenIdConnectAuthentication(CreateOptionsFromSiteConfig(config));
        }
    }

    private OpenIdConnectAuthenticationOptions CreateOptionsFromSiteConfig(B2CConfig config)
    {
        OpenIdConnectAuthenticationOptions options = new OpenIdConnectAuthenticationOptions();

        // For each policy, give OWIN the policy-specific metadata address, and
        // set the authentication type to the id of the policy
        options.MetadataAddress = string.Format(_aadInstance, _tenant, config.Policy);
        options.AuthenticationType = config.Policy;

        options.RedirectUri = config.AzureReplyUri;
        options.PostLogoutRedirectUri = config.RedirectAfterLogoutUri;

        options.Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            AuthenticationFailed = AuthenticationFailed,
            RedirectToIdentityProvider = notification =>
            {
                return Task.FromResult(notification.ProtocolMessage.UiLocales = config.UiLocale ?? string.Empty);
            },
            SecurityTokenValidated = notification =>
            {
                notification.AuthenticationTicket.Identity.AddClaim(new Claim("idp", "azureb2cad"));
                return Task.FromResult(0);
            }
        };

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

        return options;
    }

    /// <summary>
    /// Catch any failures received by the authentication middleware and handle appropriately
    /// ref - https://github.com/MicrosoftDocs/azure-docs/blob/master/articles/active-directory-b2c/active-directory-b2c-devquickstarts-web-dotnet-susi.md
    /// </summary>
    /// <param name="notification"></param>
    /// <returns></returns>
    private Task AuthenticationFailed(AuthenticationFailedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
    {
        notification.HandleResponse();

        // Handle the error code that Azure AD B2C throws when trying to reset a password from the login page
        // because password reset is not supported by a "sign-up or sign-in policy"
        if (notification.ProtocolMessage.ErrorDescription != null && notification.ProtocolMessage.ErrorDescription.Contains("AADB2C90118"))
        {
            SsoLogger.Warn("User triggered reset password");
            notification.Response.Redirect(SsoConfiguration.Routes.ResetPassword);
        }
        else if (notification.Exception.Message == "access_denied")
        {
            notification.Response.Redirect("/");
        }
        else
        {
            SsoLogger.Warn("AuthenticationFailed", notification.Exception);
            notification.Response.Redirect(SsoConfiguration.Routes.LoginError);
        }

        return Task.FromResult(0);
    }
}

However, I am seeing these errors in the error-log, after my enduser logs in:

25072 16:04:18 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.
Exception: System.InvalidOperationException
Message: 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.
Source: Sitecore.Owin.Authentication
   at Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.ValidateIdentity.CheckIdentityProvider.Process(ValidateIdentityArgs args)
 at (Object , Object )

What am I missing? How do I add the "idp" claim to my users? I already tried to add it in my Azure policy ("Identity provider claim).

  • Hi, If you are still facing same issue, please update the question with configuration for identityProviders under federatedAuthentication node. You might be missing "Idp Claim" transformation. – Arvind Gehlot May 9 at 7:17
  • Hi @ArvindGehlot. I updated the original question - I am not sure if it is a bit of a hack, but it adds the "Idp" claim to the transformation. It solves the error, but then I got a new error - but only when I try to login into Sitecore. I created a separate SO question for that: sitecore.stackexchange.com/questions/18492/… – Hos May 9 at 7:58
1

From what I can see, you are probably using Sitecore 9.0 or 9.1. Based on that assumption, your implementation is not correct.

For Sitecore 9.0 and 9.1, you should used federated authentication to let endusers login. We use the OpenID Connect protocol and WSFed to connect external identity providers to Sitecore. From what I understand, that is the same thing that you try to achieve.

With Azure AD B2C you could easily use the OIDC protocol,

For Sitecore 9.0, you basically have to create a new IdentityProvider processor, which basically adds the suitable Owin middleware to the OWIN pipeline. In my blog I described how to do this. It's making use of virtual Sitecore users, so they are not persisted

With SItecore 9.1 you have two options: use the out of the box identity provider, based on identity server 4 in which you could configure the Azure AD B2C connection (based on OIDC, again), or you could choose to ditch that provider and go for a native implementation, following the guidelines for Sitecore 9.0

Your error, btw, is probably due to the fact that the claimset isn't correctly transformed, which measn that the IDP is not added to the cookie. That cookie contains an encrypted claimset, which gets decrypted every request, to retrieve the identity from it.

  • Thanks! You are correct, it is for sitecore 9.1. I actually also tried to follow your guide, but in the end I got the same error message. I still end up missing the IDP claim. I will however, take another look at your blog post. – Hos Apr 30 at 20:24
  • I based a solution on your input but got some new errors in the log. I am waiting for assistance from Sitecore support. When I got a working solution I will either mark your answer as the solution or add the solution in here – Hos May 3 at 7:37
  • Managed to solve the errors so I have a working solution now, see sitecore.stackexchange.com/questions/18492/… for working code. Accepted the answer from @Bas Lijten because it sent me in the right direction – Hos May 11 at 18:31

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.