Recently, a powerful new validation rule was added to Laravel to enforce password strength and security. There have been several great articles on how to use it, but this article will dive deeper and shed some light on how it works internally.
First, if you want to follow along in the source code, you can find it all in this single file.
The simplest way to use this rule is
Password::min(8), which just enforces a minimum length of 8 characters. If that’s all you were going to do, you’d probably just use the
'min:8' rule though. Where this new password rule gets interesting is when you chain multiple password-related constraints, like:
<?php Password::min(8) ->letters() ->mixedCase() ->numbers() ->symbols() ->uncompromised();
But what about this? Would this work?
<?php Password::letters() ->min(8) ->mixedCase() ->numbers() ->symbols() ->uncompromised();
All we did was change the order of the first two rules in the chain, but this will not work. PHP will throw an
ErrorException. The reason is that
min() is marked as a
static function, but
letters() and all the others in that example are not. So
min() has to be specified first in the chain.
I like this API design from a logical perspective.
min() is a reasonable rule that should be set on every password field, so it’s a good choice for an entry point to this class.
If we dig a little deeper into the evaluation of the rules, we see an additional reason why this design makes sense. Taking our same example rule, what errors would you expect to be returned if I give it the password
1234. It’s too short, it doesn’t have letters, mixed case or symbols. And it’s certainly a compromised password.
Instead of getting all those errors back, we only get:
<?php [ "The password must be at least 8 characters.", ]
Why? Internally the
passes() method segments all the different rules into 3 different phases of pass/fail:
min fails, it just bails and returns the one message. If we change our password to
12345678, now we get:
<?php [ "The password must contain at least one uppercase and one lowercase letter.", "The password must contain at least one letter.", "The password must contain at least one symbol.", ]
The other rules are triggered now that
min is satisfied, but notice we still have no mention of the password being compromised. That’s because
uncompromised is only evaluated if every other password rule passes. Again, this is a perfect design because that
uncompromised rule makes an API call. Why waste the effort to do that if the password has already failed for another reason?
For completeness, let’s show the error from the
uncompromised rule. Let’s say we enter the super secure password of
Password1!. It satisfies minimum length, and all the other conditions, but we now get this error:
<?php [ "The given password has appeared in a data leak. Please choose a different password.", ]
And in case you’re wondering,
Password4!, etc are all compromised as well. As of the writing of this article,
Password12345678! is still uncompromised though, so have fun with that.