Umbraco Strong Password Policy

Umbraco v7 standard password policies are pretty relaxed: any character, 8 or more.

I recently worked on enforcing a stronger password policy, and it was far from easy, unless you know exactly what to do, which you are about to find out.

You have a problem. You choose to solve it with regex. Now you have 2 problems.

In the web.config you’ll find 2 membership providers, one for backend users and one for “members”. You can read more about it in this documentation.

Right there you can change the minimum number of characters and even specify a regex. How hard can it be to check for lowercase, uppercase, digits and symbols in regex?

Let’s not work hard reinvent the wheel, let’s check Stack Overflow. The most promising thread there didn’t offer a perfect solution there, so I’ve added my own. A classic developer problem explained best by xkcd in this comic:

Turns our you need a quadruple positive lookahead followed by a length check.

^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*([^a-zA-Z\d\s])).{9,}$

Pretty isn’t it?

For the uninitiated: regex consumes characters as it goes on, to check the same string for multiple different things I need to use a lookahead (?=) for each, so this regex would read something like make sure that between the beginning (^) and the end ($) of the string, following zero or more of any character (.) there is a lowercase string ([a-z]), also do that for uppercase ([A-Z]), digits (\d) and finally a symbol which I defined as anything that is not (^) one of the previous or a whitespace(\s). At this point the regex has consumed no character yet, so I check that there are at least 9 ({9,}) of anything (.).

E.A.S.Y.

It took me quite a few hours to perfect this, aided by my always faithful Regex101, where I added strings that this regex shouldn’t match, and strings that should match.

It works perfectly!

Ship it!

Too bad that if the user doesn’t choose a password strong enough, they receive the error:

Not very user friendly, is it?

We need to find another way

Skimming thought Umbraco source I notice that the validator can do all the checks I need, but it disables them in the constructor.

The only way I’ve found to change this, was to override just enough of the owin startup so we can set these checks on.

In the following code I’m inheriting from the default owin startup and mostly copying what it’s doing, substituting as little code as possible to allow me to call SetPasswordRequirements, which is where all the checks are set.

using Microsoft.Owin;
using Owin;
using Umbraco.Core;
using Umbraco.Core.Security;
using Umbraco.Web.Security.Identity;
using Umbraco.Core.Models.Identity;
using Website.Web;
using Umbraco.Web;

//To use this startup class, change the appSetting value in the web.config called 
// "owin:appStartup" to be "UmbracoCustomOwinStartup"

[assembly: OwinStartup("UmbracoCustomOwinStartup", typeof(UmbracoCustomOwinStartup))]

namespace Website.Web
{
    /// <summary>
    /// A custom way to configure OWIN for Umbraco
    /// </summary>
    /// <remarks>
    /// The startup type is specified in appSettings under owin:appStartup - change it to "UmbracoCustomOwinStartup" to use this class
    /// 
    /// This startup class would allow you to customize the Identity IUserStore and/or IUserManager for the Umbraco Backoffice
    /// </remarks>
    public class UmbracoCustomOwinStartup : UmbracoDefaultOwinStartup
    {
        public new void Configuration(IAppBuilder app)
        {
            app.SanitizeThreadCulture();
            this.ConfigureServices(app);
            this.ConfigureMiddleware(app);
        }

        /// <summary>
        /// Configures services to be created in the OWIN context (CreatePerOwinContext).
        /// </summary>
        /// <param name="app"></param>
        protected new void ConfigureServices(IAppBuilder app)
        {
            app.SetUmbracoLoggerFactory();
            this.ConfigureUmbracoUserManager(app);
        }

        /// <summary>
        /// Configure the Identity user manager for use with Umbraco Back office.
        /// Additionaly also sets stronger password requirements.
        /// </summary>
        protected new void ConfigureUmbracoUserManager(IAppBuilder app)
        {
            var applicationContext = ApplicationContext.Current;

            app.ConfigureUserManagerForUmbracoBackOffice<BackOfficeUserManager, BackOfficeIdentityUser>(
                applicationContext,
                (options, context) =>
                {
                    var membershipProvider = MembershipProviderExtensions
                        .GetUsersMembershipProvider()
                        .AsUmbracoMembershipProvider();
                    var settingContent = Umbraco.Core.Configuration.UmbracoConfig.For.UmbracoSettings().Content;
                    var userManager = BackOfficeUserManager.Create(options,
                        applicationContext.Services.UserService,
                        applicationContext.Services.EntityService,
                        applicationContext.Services.ExternalLoginService,
                        membershipProvider,
                        settingContent);

                    SetPasswordRequirements(userManager);

                    return userManager;
                });

        }

        private void SetPasswordRequirements(BackOfficeUserManager backOfficeUserManager)
        {
            // If this doesn't work we want to fail hard.
            var membershipProviderPasswordValidator = (MembershipProviderPasswordValidator)backOfficeUserManager.PasswordValidator;

            // Set strong password policy.
            membershipProviderPasswordValidator.RequireDigit = true;
            membershipProviderPasswordValidator.RequireLowercase = true;
            membershipProviderPasswordValidator.RequireNonLetterOrDigit = true;
            membershipProviderPasswordValidator.RequireUppercase = true;
            membershipProviderPasswordValidator.RequiredLength = 9;
        }
    }
}

Don’t forget to set this as the configured owin startup in the web.config and finally not only you should have a strong password validation, but also very specific messages on what the rules are.

Notes

The validation set in the web.config takes priority over this, and it should be set according to this, so you won’t get inconsistent messages. E.g. if you type 7 characters by default it would tell you that you need 8, if the you type 8 this validator would kick in and would tell you that actually you need 9.

This will probably all change in Umbraco v8.