Laravel validation rule — passwords

mattkingshott profile image Matt Kingshott 👨🏻‍💻 Originally published at Medium on ・3 min read

Image courtesy of Unsplash

Laravel validation rule — passwords

In this new series, we’ll be exploring the concept of custom validation rules in Laravel and why you’ll want to use them. I’m aiming to release a new article each day containing a new rule which you can use in your own projects. I’ll also be collecting these rules into a package that you can pull in via composer.


So, why would you want to create custom rules? Well, there are a number of benefits to using bespoke validation classes. They include:

  1. Encapsulating validation logic in a separate file instead of littering your controllers, or other areas of your domain with data validation.
  2. Rules become reusable. You can use them in multiple places within your project, or even in multiple projects.
  3. Rules are easily testable. Simply create a validator instance and verify the expected result against some provided data.
  4. Rule messages can be localised. Simply use the native trans method to return the appropriate string of text.


I’ve recently released Pulse — a friendly, affordable server and site monitoring service designed specifically for developers. It includes all the usual hardware monitors, custom service monitors, alerting via various notification channels, logs, as well as full API support. Take a look… https://pulse.alphametric.co

Server and Site Monitoring with Pulse

Creating a rule

Okay, so let’s dig into the code required to make a custom validation rule. In this instance, we’ll be creating a rule that ensures a user has provided a strong password e.g. during the registration process.

We’ll begin by creating a new validation rule using the helpful out-of-the-box Artisan command provided by Laravel:

php artisan make:rule StrongPassword

This will create a new class in the App\Rules namespace. This class contains two methods, passes and message.

Passes contains the validation logic used to determine if a supplied value passes the rule check, while message allows us to execute whatever code is needed to respond with an error message when the check fails.

Defining the check

Let’s begin by writing the logic necessary to ensure that the user has provided a strong password. Per current conventions, a password is usually considered strong when the following criteria are met:

  1. The password must be a minimum of 12 characters in length. I also set a maximum of 30 characters to ensure compatibility with bcrypt.
  2. The password must include an upper case and a lower case letter.
  3. The password must include a number.
  4. The password must include a symbol.

The easiest way to enforce all of these requirements, is to use a regular expression, so let’s populate the passes method with an appropriate one:

public function passes($attribute, $value)
    return preg\_match("/^(?=.\*?[A-Z])(?=.\*?[a-z])(?=.\*?[0-9])(?=.\*?[#?!@()$%^&\*=\_{}[\]:;\"'|\\<>,.\/~`±§+-]).{12,30}$/", $value);

Handling failures

The final step, is to add a message that should be returned when the provided value does not satisfy the regular expression. We’ll hard code a string for this, but you could use trans or any logic you wanted to create the response.

public function message()
    return 'The :attribute must be 12–30 characters, and include a number, a symbol, a lower and a upper case letter';

Testing the rule

Since we’re good developers, we should write a test to confirm that the rule behaves as expected. Fortunately, the validator helper makes this a breeze:

/\*\* @test \*/
public function a\_password\_can\_be\_validated()
    $rule = ['password' => [new App\Rules\StrongPassword]];

    $this->assertFalse(validator(['password' => '1'], $rule)->passes());

    $this->assertFalse(validator(['password' => '1#'], $rule)->passes());

    $this->assertFalse(validator(['password' => 'a1#'], $rule)->passes());

    $this->assertFalse(validator(['password' => 'aB1#'], $rule)->passes());

    $this->assertFalse(validator(['password' => 'Ertbyrt123#'], $rule)->passes());

    $this->assertTrue(validator(['password' => 'Ertbyrt1234#'], $rule)->passes());


Wrapping Up

We now have a reusable validation rule to ensure that users supply a strong password when we require it. We also respond with a suitable error message when the check fails, and we have a test to ensure that rule works correctly.

You can see the complete class and pull it into your projects by visiting the repo on Github: https://github.com/alphametric/laravel-validation-rules

As I mentioned at the beginning, I have additional validation rules that I intend to share with you, so be sure to follow me for those articles. If you’re interested, you can also follow me on Twitter to see everything I’m up to.

Happy coding!

Posted on Mar 20 '19 by:

mattkingshott profile

Matt Kingshott 👨🏻‍💻


Founder. Developer. Writer. Lunatic. Created Pulse, IodineJS, Axiom, and more. #PHP #Laravel #Vue #TailwindCSS


markdown guide

Actually, the Bcrypt algorithm supports up to 72 bytes(at least in the original implementation). You should set it to that. In my opinion you shouldn't be enforcing anything else other than a minimum length.

You don't have to set a maximimum length for the password, even if Bcrypt truncates at 72 characters. It trims any bytes after 72. You could for performance reasons to have the maximum length set.

Having stricter password rules can help attackers make more educated guesses on what the password may be. I advice against password rules like this.

While Bcrypt has been around for a lot longer, theoretically Scrypt and Argon2 are computationally more resistant to attacks, and is now recommended by cryptography experts.

Also, Argon2 doesn't have a character limit as far as I know.

On a note, I suggest you recommend password managers(KeePassXC, BitWarden) and diceware passphrases during the process of signing up/in.

Optionally, you should check if the provided password(s) have been PWNED! here. BUT LET THE INDIVIDUAL OPT-IN TO CHECK. DO NOT DO THIS WITHOUT CONSENT.


Thanks for the reply.

There's some interesting details here. I'm going to look into it some more...