Reset Your Sitecore Accounts After a Configurable Amount of Time Has Passed

A common question raised in the community is, “How do I unlock a Sitecore account due to bad password attempts?”. The fix is easy enough with a simple SQL command, but why not use a task that will do this for you?


How to Lock Accounts After Failed Password Attempts

The setting to lock accounts will be found in the web.config file, under the membership section. To test, I've set mine to be very short, with maxInvalidPasswordAttempts to be 3, and passwordAttemptWindow is 2. This means the account will lock after 3 failed attempts inside a 2 minute window.

<membership>
  <providers>
    <clear />
    <add name="sql" type="System.Web.Security.SqlMembershipProvider" 
      connectionStringName="core" 
      applicationName="sitecore" 
      maxInvalidPasswordAttempts="3" 
      passwordAttemptWindow="2" />
  </providers>
</membership>

I intentionally failed a login three times after applying this setting, and checked the Core database with the following query:

SELECT 
[Email],[IsLockedOut],[LastLockoutDate],[FailedPasswordAttemptCount] 
FROM 
[aspnet_Membership] 
where IsLockedOut = 1

This returns the result:

  • Email: user@domain.com
  • IsLockedOut: 1
  • LastLockoutDate: 2023-11-19 7:00:00 PM
  • FailedPasswordAttemptCount: 3

That's exactly what we want. The account is now locked until someone does something about it.


The Quick Fix

Most answers out there will be to just run the following command, which works just fine. The problem is you need a developer to do this every time.

UPDATE [aspnet_Membership] set 
FailedPasswordAttemptCount=0, 
IsLockedOut=0 
where 
email='user@domain.com' and IsLockedOut = 1

Ta-Da, your account is ready to be used again! But this can be improved. Let's add a scheduled task to do the work for you.


Automating Account Unlocks

The first thing to do is set up a task. I've already posted about this, but let's just drop a quick and easy config file to run a process once a minute. We'll also add a value for ResetAfterMinutes, which resets locked accounts after the desired time span.

<configuration>
  <sitecore>
    <settings>
      <setting name="SitecoreFundamentals.Tasks.UserAccountLockoutReset.ResetAfterMinutes" 
        value="30" role:require="Standalone or ContentManagement"/>
    </settings>
    <scheduling>
      <agent type="SitecoreFundamentals.Tasks.UserAccountLockoutReset, SitecoreFundamentals" 
        method="Run" interval="00:01:00" 
        role:require="Standalone or ContentManagement">
        <donotunlockifcontains hint="list">
          <item>sitecore\</item>
        </donotunlockifcontains>
      </agent>
    </scheduling>
  </sitecore>
</configuration>

Now we need a task. It's pretty easy; we'll just iterate over locked accounts and reset those which have been in detention long enough. The list of donotunlockifcontains is optional, and would ignore locked accounts matching their content.

namespace SitecoreFundamentals.Tasks
{
    public class UserAccountLockoutReset
    {
        public List<string> DoNotUnlockIfContains { get; private set; }
        public UserAccountLockoutReset()
        {
            this.DoNotUnlockIfContains = new List<string>();
        }
        public bool Run()
        {
            var logPrefix = $"[{GetType().FullName}.{MethodBase.GetCurrentMethod().Name}] -> ";
            int resetAfterMinutes = Sitecore.Configuration.Settings.GetIntSetting("SitecoreFundamentals.Tasks.UserAccountLockoutReset.ResetAfterMinutes", 30);
            var resetTime = DateTime.UtcNow.AddMinutes(resetAfterMinutes * -1);
            var lockedUsersNeedingReset = Membership.GetAllUsers().Cast<membershipuser>()
                .Where(x => x.IsLockedOut 
                && !DoNotUnlockIfContains.Any(p => x.UserName.Contains(p)));
            try
            {
                foreach (var membershipUser in lockedUsersNeedingReset)
                {
                    if (membershipUser.LastLockoutDate <= resetTime)
                    {
                        Log.Info($"{logPrefix} Unlocking {membershipUser.UserName}", this);
                        membershipUser.UnlockUser();
                    }
                    else
                    {
                        var timeToUnlock = membershipUser.LastLockoutDate - resetTime;
                        var formattedTimeToUnlock = string.Format("{0:0}.{1:00}", Math.Truncate(timeToUnlock.TotalMinutes), timeToUnlock.Seconds);
                        Log.Info($"{logPrefix} {membershipUser.UserName} will be unlocked in {formattedTimeToUnlock} minutes.", this);
                    }
                }
            }
            catch (Exception ex)
            {
                Log.Error($"{logPrefix} {ex}", this);
                return false;
            }
            return true;
        }
    }
}

That should do it!  The User of accounts that are locked for longer than 30 minutes are reset using the UnlockUser() method. You get the following log entries each time this task runs:

14:21:06 INFO  Job started: SitecoreFundamentals.Tasks.UserAccountLockoutReset
14:21:06 INFO  [SitecoreFundamentals.Tasks.UserAccountLockoutReset.Run] ->  extranet\user@domain.com will be unlocked in 28.34 minutes.
14:21:06 INFO  Job ended: SitecoreFundamentals.Tasks.UserAccountLockoutReset (units processed: )

And the following will be logged when an account is unlocked:

14:22:06 INFO  Job started: SitecoreFundamentals.Tasks.UserAccountLockoutReset
14:22:06 INFO  [SitecoreFundamentals.Tasks.UserAccountLockoutReset.Run] ->  Unlocking extranet\user@domain.com
14:22:06 INFO  Job ended: SitecoreFundamentals.Tasks.UserAccountLockoutReset (units processed: )