Q&A
How to strengthen password validations in Laravel
Overview
Weak passwords are a top credential-attack vector. Laravel's Illuminate\Validation\Rules\Password class (available since Laravel 9) makes it easy to enforce a layered policy in one readable chain - length, complexity, and a breach check - and works exactly like any other validation rule.
graph TD
A["Password::min(8)"] --> B[".letters()"]
B --> C[".mixedCase()"]
C --> D[".numbers()"]
D --> E[".symbols()"]
E --> F[".uncompromised()"]
F --> G[Validation passes / fails with messages]
style A fill:#1e1b4b,color:#fff
The Password Rule Object (Laravel 9+)
use Illuminate\Validation\Rules\Password;
$request->validate([
'password' => [
'required',
'confirmed', // must match 'password_confirmation' field
Password::min(8) // at least 8 characters
->max(128) // prevent excessive-length DoS
->letters() // must contain letters
->mixedCase() // uppercase AND lowercase
->numbers() // at least one digit
->symbols() // at least one symbol (!@#...)
->uncompromised(), // not found in known breach databases
],
]);
Each method in the chain adds a requirement; all must pass. The automatic error messages are localization-friendly.
What Each Constraint Does
| Constraint | Requirement |
|---|---|
min(n) |
Minimum length |
max(n) |
Maximum length |
letters() |
At least one letter |
mixedCase() |
At least one uppercase + one lowercase |
numbers() |
At least one digit |
symbols() |
At least one special character |
uncompromised($times) |
Not in Have I Been Pwned database (uses k-anonymity, privacy-safe) |
Setting a Default Password Rule App-Wide
Define your standard rule once in AppServiceProvider and reference it everywhere:
// app/Providers/AppServiceProvider.php
use Illuminate\Validation\Rules\Password;
public function boot(): void
{
Password::defaults(fn () =>
app()->environment('production')
? Password::min(8)->mixedCase()->numbers()->symbols()->uncompromised()
: Password::min(8) // relaxed in local dev for convenience
);
}
Then in any request:
'password' => ['required', 'confirmed', Password::defaults()],
A single change in boot() applies site-wide - no hunting down every validation rule.
Custom Validation Rules
For organization-specific policies (e.g. no repeating characters, no username in password), add a custom rule:
use Illuminate\Validation\Rules\Password;
use Illuminate\Contracts\Validation\Rule;
class NotContainsUsername implements Rule
{
public function passes($attribute, $value): bool
{
return !str_contains(strtolower($value), strtolower(auth()->user()->name));
}
public function message(): string { return 'Password must not contain your username.'; }
}
// In validation:
'password' => ['required', 'confirmed', Password::min(8)->symbols(), new NotContainsUsername],
Form Request (cleanest location for rules)
// app/Http/Requests/RegisterRequest.php
public function rules(): array
{
return [
'email' => 'required|email|unique:users',
'password' => ['required', 'confirmed', Password::defaults()],
];
}
Moving validation into a FormRequest class keeps controllers lean and rules testable.
Bcrypt / Hashing (side note)
Strong validation only matters if passwords are stored correctly. Always hash with:
bcrypt($request->password)
// or:
Hash::make($request->password)
Never store plaintext or MD5/SHA1 hashes. Laravel uses bcrypt (or Argon2 if configured) automatically.
Why This Matters in Interviews
Security interviews use this to assess real-world input validation knowledge. Cover: the Password rule object chain (min, mixedCase, numbers, symbols, uncompromised), setting app-wide defaults in AppServiceProvider, custom Rule classes for bespoke policies, confirmed for confirmation matching, and always hashing with bcrypt/Argon2. The uncompromised() k-anonymity breach check is a strong signal of security awareness.
Common Pitfalls
- Setting a maximum that's too low - preventing strong passphrases (allow at least 128 chars).
- Forgetting
confirmedvalidation when a confirm-password field is shown. - Storing unhashed passwords or using weak hashes (MD5/SHA1).
- Overly strict symbols requirements that block valid Unicode or passphrase-style passwords.
- Not setting app-wide defaults, leading to inconsistent policies across registration, password-reset, and profile-update flows.
References
- Laravel
Passwordvalidation rule: https://laravel.com/docs/validation#validating-passwords - Have I Been Pwned API (used by
uncompromised): https://haveibeenpwned.com/API/v3