langleyfoxall/laravel-nist-password-rules
Laravel validation rules implementing NIST SP 800-63B password guidance: enforce minimum length, reject breached and common dictionary passwords, block context-specific words, and avoid outdated composition rules. Easy to add to form requests and validators.
composer require langleyfoxall/laravel-nist-password-rules:^8.2. v8.x targets modern Laravel/PHP; check the composer.json constraints before dropping it into an older app.config/app.php on Laravel 5.5+.src/PasswordRules.php. That single static helper class is the whole public API surface for most projects; the individual rule classes live under src/Rules/ and are what the helper returns.min:8|confirmed|regex:... rules with the package's bundle:use LangleyFoxall\LaravelNISTPasswordRules\PasswordRules;
$request->validate([
'password' => PasswordRules::register(8),
]);
confirmed, required, and max: without any glue code:'password' => array_merge(['required', 'confirmed', 'max:255'], PasswordRules::register(8)),
^8.2. The rest of this assessment is the initial baseline for the package as a whole.Pick the right helper for the flow. Registration and password-change have different requirements (change usually needs a username/email block and reuse prevention), and the package exposes separate helpers rather than one overloaded method:
// Account creation
'password' => PasswordRules::register(8),
// Password reset / change
'password' => PasswordRules::changePassword($user->email, 8),
The *WithContext variants additionally reject passwords containing the username/email or other context values. Use them anywhere an identity is already known — registration with an email field, invite acceptance, admin-created users:
'password' => PasswordRules::registerWithContext($request->email, 8),
'password' => PasswordRules::changePasswordWithContext($user->email, 8),
Put the rules in Form Requests, not controllers. These rule sets are long enough that inline validation gets noisy, and Form Requests give you one place to swap register for changePassword and to keep the authorize() logic next to it.
Use the rule objects directly when you need surgical control. The helper is a convenience bundle; each check is a standalone Illuminate\Contracts\Validation\Rule. If you only want the HaveIBeenPwned check without the dictionary/common-password checks, instantiate the individual rule classes from src/Rules/ and mix them with Laravel's native Password::min(). This is also the escape hatch when a bundle is too strict for a legacy system you're tightening incrementally.
Align with Password::defaults() deliberately. Laravel's own password builder and this package overlap on length and on rejecting compromised passwords. Pick one as the source of truth — typically this package for the NIST-specific checks, plus Password::defaults() only for uncompromised() if you don't want the bundled list. Running both is harmless but doubles validation messages and outbound HTTP calls.
Fortify / Breeze / Jetstream. These publish their own PasswordValidationRules trait or CreateNewUser action. Replace the Password::min(8) return value with PasswordRules::register(8) there — that is the single integration point in most apps, and it means every entry path (register, reset, update) gets consistent checks.
Testing. The compromised-password rule makes an outbound HTTP call. Wrap feature tests that hit it with Http::fake() and a canned range response, or set the test environment to skip the network check. Without this, your CI suite gets slow and flaky the moment the API hiccups.
Http::fake(['api.pwnedpasswords.com/*' => Http::response("0018A45C4D1DEF81644B54AB7F969B88D65:1\r\n", 200)]);
Localization. Validation messages ship with the package. If your app is not English, publish the package's language files (php artisan vendor:publish --tag=..., confirm the exact tag in the README) and translate them rather than overriding messages ad hoc at each call site.
regex:/[A-Z]/ and different:old_password-style rotation alongside this package, you have negated the reason you installed it. Audit the surrounding rules when you adopt it.max generously. A common mistake is max:20 inherited from an old form. NIST says accept long passphrases; allow at least 64 (255 is a safe ceiling) and only reject on server-side limits you actually enforce in the column.changePassword does not enforce reuse history. The package checks quality, not whether the user already used that password. If you need "not one of your last N", implement it yourself against a password history table — don't assume the helper covers it.password_hash column or a legacy bcrypt call truncates at 72 bytes, a long passphrase is silently weakened. Ensure the max rule and the column length agree with your hashing strategy.dd(PasswordRules::register(8)) in a Tinker session to see the concrete array. It's usually an ordering or a stray string rule, not the package.Rule implementation and appending it to the returned array is cleaner than forking the package.^8.2 rather than * so a future major doesn't land unreviewed in a deploy.How can I help you explore Laravel packages today?