Sitecore 10 AD Integration:  Infinite redirect loop when an Azure AD user is disabled in Sitecore

Sitecore 10 AD Integration: Infinite redirect loop when an Azure AD user is disabled in Sitecore

As mentioned above, If an Azure AD user is disabled in Sitecore, they receive endless redirects when they try to log in.

Based on the kb article (support.sitecore.com/kb?id=kb_article_view&..), the known issue 235962 was fixed in Sitecore XP 9.2.

Background

I've logged a ticket to Sitecore support team and confirm this is a known issue and reoccurred.

Unfortunately, there are limitations that prevent Sitecore product team from providing a solution for Sitecore 10.

Technically it is not possible to fix the problem due to the design of the sign-in model in Microsoft Identity libraries. When we are disabling a user in User Manager this operation sets IsApproved field to false in [aspnet_Membership] table. From MS SignInManager's perspective, this is not an obstacle to signing in the user. Unfortunately, the method cannot be overridden to change this behavior.

Sitecore provided a few possible workarounds:

  1. Instead of disabling users in User Manager, we change the IsLockedOut field in [aspnet_Membership] table to "1" with some external tool or with SQL script.
  2. Block AzureAd accounts on the Azure portal.
  3. If #2 is not acceptable (e.g. accounts can be used on other systems along with Sitecore instance) then it is possible to organize access through groups. When a user should not have access to Sitecore the user account should be removed from the group on the Azure AD side.

In my case, the option 1 make more sense as Azure AD is for an user to access multiple Systems. For Example:
If an user using AD to access System A, System B and Windows/Mac OS. The user use System A and logged in to Windows OS everyday but he did not access to System B (Sitecore). Sitecore will run daily task to disable users who does not login more than 90 days, then option B to disable accounts on Azure Portal is out.

However, IsLockedOut property is a read only field, unable to update the user easily. There is a couple of ways below:

  1. You can increment the value of the MaxInvalidPasswordAttempts and this will lead to the user lock.
  2. You can implement a code that updates the IsLockedOut property in the SQL database directly.

My Workaround

If you implement the option 1 workaround from Sitecore team, it does not solve the problem entirely. Even we can change IsLockedOut to True, we still encounter the same problem. I've tested, If user is locked out then the user will get redirect to /sitecore/service/error.aspx?error=User is locked out.

The error.aspx page was flag out from VAPT report, we was asked to remove it because it is medium risk content injection.

The end, I've created a pipeline processor after SignIn.ResolveUser to check user status. If user status is disabled then system will redirect user to a custom error page with valid error message.

<pipelines>
<owin.cookieAuthentication.signIn>
<processor patch:after="*[@type='Sitecore.Owin.Authentication.Pipelines.CookieAuthentication.SignIn.ResolveUser, Sitecore.Owin.Authentication']" type="Sitecore.Custom.Foundation.SitecoreExtensions.Owin.Authentication.Pipelines.ValidateUserStatus, Sitecore.Custom.Foundation.SitecoreExtensions" resolve="true"/>
</owin.cookieAuthentication.signIn>
</pipelines>
<settings>
            <!--   ERROR HANDLER for User Disabled -->
            <setting name="ErrorPage.UserDisabled" value="/sitecore/login/disabled.aspx"/>
</settings>
namespace Sitecore.Custom.Foundation.SitecoreExtensions.Owin.Authentication.Pipelines
{
    public class ValidateUserStatus : SignInProcessor
    {
        public ValidateUserStatus(ApplicationUserFactory applicationUserFactory, UserFactory userFactory)
        {
            Assert.ArgumentNotNull((object)applicationUserFactory, nameof(applicationUserFactory));
            Assert.ArgumentNotNull((object)userFactory, nameof(userFactory));
            this.ApplicationUserFactory = applicationUserFactory;
            this.UserFactory = userFactory;
        }

        protected ApplicationUserFactory ApplicationUserFactory { get; }

        protected UserFactory UserFactory { get; }

        public override void Process(SignInArgs args)
        {
            Assert.ArgumentNotNull((object)args, nameof(args));
            if (args.User.InnerUser.Profile.State == "Disabled" || args.User.InnerUser.Profile.State == "Locked Out")
            {
                args.Success = false;
                args.AbortPipeline();
                args.HttpContext.Session.Abandon();
                args.HttpContext.Session.RemoveAll();
                var errorPage = Sitecore.Configuration.Settings.GetSetting("ErrorPage.UserDisabled");
                args.HttpContext.Response.Redirect(errorPage);
            }

            return;
        }
    }
}

As of the error page, we can clone the login page from \sitecore\login\default.aspx and modify the page to display the static error message and remove the login form. Please see screenshot below for my end result. image.png

As for Sitecore official patch, the fix will be considered for the next versions of Sitecore