groupesti / laravel-password
Password policy and lifecycle for Laravel: extended validation rule, blocklists, reuse prevention, expiration, compromise detection and notifications.
Requires
- php: ^8.2
- ext-mbstring: *
- illuminate/database: ^11.0 || ^12.0 || ^13.0
- illuminate/notifications: ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^11.0 || ^12.0 || ^13.0
- illuminate/validation: ^11.0 || ^12.0 || ^13.0
- ramsey/uuid: ^4.9.3
Requires (Dev)
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpunit/phpunit: ^10.5 || ^11.0 || ^12.0
Suggests
- ext-intl: Enables NFKC normalization in the normalize() rule.
README
Password policy and lifecycle for Laravel: a validation rule that extends
Illuminate\Validation\Rules\Password, blocklists (file + database), password
reuse prevention, expiration / forced change, breach detection (HaveIBeenPwned)
and user/administrator notifications — fully configurable and translated.
Installation
composer require groupesti/groupesti-password php artisan vendor:publish --tag=password-config php artisan migrate
For the lifecycle columns on the users table:
php artisan vendor:publish --tag=password-migrations php artisan migrate
The validation rule
use Password\Password; $request->validate([ 'password' => Password::min(12) ->letters(1) // >= 1 letter ->mixedCase(1) // >= 1 uppercase and >= 1 lowercase ->numbers(2) // >= 2 digits ->symbols(1) // >= 1 symbol ->upperCase(1, 3) // min/max bounds per case ->allowSpaces(false) ->maxConsecutive(3) // no more than 3 identical characters in a row ->maxRepeats(4) // a character at most 4 times ->maxSequential(4) // no run (abc / 123) longer than 4 ->noKeyboardPatterns() // forbid azerty/qwerty ->notContainsUserInfo() // forbid name/email/username ->notContains() // forbidden-word dictionary ->normalize() // NFKC + reject control characters ->uncompromised() // HaveIBeenPwned ->blocklists(), // internal file + DB blocklists ]);
Every character-class method follows the signature (?int $min = 1, ?int $max = null):
a bare call requires "at least 1", null disables the bound.
Default policy
Password::defaults() builds the rule from config/password.php:
'password' => Password::defaults(),
Utilities
Password::generate(); // random policy-compliant password Password::strength($password); // ['bits' => 84, 'level' => 'very_strong', 'score' => 4]
Lifecycle (history, expiration, forced change)
On your User model:
use Password\Traits\ManagesPasswordLifecycle; use Password\Interfaces\PasswordLifecycle; class User extends Authenticatable implements PasswordLifecycle { use ManagesPasswordLifecycle; }
Prevent reuse (last N or N days, depending on the config):
use Password\Rules\NotReused; $request->validate([ 'password' => [Password::defaults(), NotReused::for($request->user())], ]);
On every password change: $user->markPasswordChanged();
Force a change through the middleware:
Route::middleware(['auth', 'password.require-change'])->group(/* ... */);
Real-time compromise detection + notifications
Enable password.realtime.enabled: on sign-in the password is checked against
HaveIBeenPwned and the configured action is applied (force_change | notify |
block). User and administrator notifications, as well as a scheduled digest
(daily/weekly/monthly), are configured in the notifications section.
Operational errors (an unavailable blocklist source, etc.) throw exceptions that self-report: logging + administrator alert.
Component stubs
Ready-to-use password fields (show/hide toggle, strength meter, live requirements):
php artisan vendor:publish --tag=password-stubs-blade-tailwind php artisan vendor:publish --tag=password-stubs-blade-bootstrap php artisan vendor:publish --tag=password-stubs-vue-tailwind php artisan vendor:publish --tag=password-stubs-vue-bootstrap
Translations
Provided for: en, fr, es, de, pt-BR, it, nl, ru, zh-CN, ja, ar, tr.
php artisan vendor:publish --tag=password-lang
License
MIT. See license.md.