Integrating HaveIBeenPwned into Laravel Fortify

Posted on 29 April 2021
3 minute read

The HaveIBeenPwned service provided by Troy Hunt contains a whole trove of breach information. It enables you to look up single email address, whole domains and whether a password has been seen in a data breach, for example. It's the latter one that we're interested in for this feature. Let's implement this using the icawebdesign/hibp-php framework-agostic composer package. Install this with…

composer require icawebdesign/hibp-php

HIBP uses a k-anonymity model, meaning that when you request to see if a password is contained in a breach, you don't actually send the plaintext password across the internet, but a subset of a hashed version of it, making it secure.

Laravel, when using Jetstream or Breeze starter-kits uses Fortify under the hood. This also provides a useful authentication system when implementing yourself if you're not using one of the starter kits. It allows creating new users, logging in users and updating users, amongst other things. The part we want to focus on that integrates into all 3 of these sections, though in a single area, is the password validation rules section.

If we look at the default rules code, we see

<?php

namespace App\Actions\Fortify;

use App\Rules\Hibp;
use Laravel\Fortify\Rules\Password;

trait PasswordValidationRules
{
    /**
     * Get the validation rules used to validate passwords.
     *
     * @return array
     */
    protected function passwordRules(): array
    {
        return ['required', 'string', new Password(), 'confirmed'];
    }
}

This allows us to add extra rules by adding them to the returned array.

Let's start by creating the rules config…

php artisan make:rule Hibp

Now that we have the rules file, we can add the lookup to the passes method…

public function passes($attribute, $value): bool
{
    $count = (new PwnedPassword(new HibpHttp()))->paddedRangeFromHash(sha1($value));

    return 0 === $count;
}

We should update the message for this too that will be returned to the user in the form validation errors…

public function message(): string
{
    return 'The :attribute has appeared in a data breach and is unsafe to use.';
}

We should make sure that the Fortify actions have been published into our code to be modified…

php artisan vendor:publish --provider="Laravel\Fortify\FortifyServiceProvider"

Now, within your app/Actions/Fortify directory, you should have access to the PasswordValidationRules.php file that we can add our new rule to. Add our Hibp rule to the end of the returned array…

<?php

namespace App\Actions\Fortify;

use App\Rules\Hibp;
use Laravel\Fortify\Rules\Password;

trait PasswordValidationRules
{
    /**
     * Get the validation rules used to validate passwords.
     *
     * @return array
     */
    protected function passwordRules(): array
    {
        return ['required', 'string', new Password(), 'confirmed', new Hibp()];
    }
}

Now, when we try to update our password with one that appears in a data breach at least once (password for example, scary, I know!), we now get a message rejecting the usage…

Laravel-HIBP

This will now work when a new user registers on your site, or a current user updates their password.