symfony/password-hasher
Symfony PasswordHasher provides secure password hashing and verification utilities. Configure multiple algorithms via PasswordHasherFactory (bcrypt, sodium/Argon2, etc.), hash plain passwords, verify hashes, and support upgrades with modern best practices.
composer require symfony/password-hasher
HashServiceProvider) to bind the Symfony factory:
use Illuminate\Support\ServiceProvider;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class HashServiceProvider extends ServiceProvider
{
public function register()
{
$factory = new PasswordHasherFactory([
'default' => ['algorithm' => 'bcrypt'],
'admin' => ['algorithm' => 'argon2id'],
]);
$this->app->singleton(UserPasswordHasherInterface::class, function () use ($factory) {
return $factory->getPasswordHasher('default');
});
}
}
config/app.php:
'providers' => [
// ...
App\Providers\HashServiceProvider::class,
],
Hash::make() with the Symfony hasher:
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
$hasher = app(UserPasswordHasherInterface::class);
$hash = $hasher->hashPassword($user, 'plain-text-password');
PasswordHasherFactory (configures algorithms)UserPasswordHasherInterface (core hashing/verification)HashUtil (utility methods for hash analysis)HashServiceProvider and UserPasswordHasherInterface binding.Use the factory to assign different algorithms to user roles:
$factory = new PasswordHasherFactory([
'users' => ['algorithm' => 'bcrypt'],
'admins' => ['algorithm' => 'argon2id', 'memory_cost' => 64],
'api_keys'=> ['algorithm' => 'sodium'],
]);
// Retrieve hasher for a role
$adminHasher = $factory->getPasswordHasher('admins');
$hash = $adminHasher->hash('secure-password');
Extend Laravel’s Hash facade to delegate to Symfony:
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Hash as LaravelHash;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
public function boot()
{
LaravelHash::macro('makeSymfony', function ($password, $user = null) {
$hasher = app(UserPasswordHasherInterface::class);
return $hasher->hashPassword($user, $password);
});
}
// Usage:
$hash = Hash::makeSymfony('password', $user);
Leverage Symfony’s needsRehash() to upgrade hashes during authentication:
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
public function login(Request $request, UserPasswordHasherInterface $hasher)
{
$user = User::find($request->user_id);
if ($hasher->needsRehash($user->password)) {
$user->password = $hasher->hashPassword($user, $request->plain_password);
$user->save();
}
// Proceed with auth...
}
Use HashUtil to analyze existing hashes (e.g., for migrations):
use Symfony\Component\PasswordHasher\Hasher\HashUtil;
if (HashUtil::checkPrefix($hash, 'bcrypt')) {
// Migrate bcrypt to Argon2
$newHasher = $factory->getPasswordHasher('argon2id');
$user->password = $newHasher->hashPassword($user, $user->password);
}
VARCHAR(255)).config/hash.php:
return [
'algorithms' => [
'default' => ['algorithm' => 'bcrypt'],
'secure' => ['algorithm' => 'argon2id', 'memory_cost' => 128],
],
];
UserPasswordHasherInterface in unit tests:
$this->partialMock(UserPasswordHasherInterface::class, ['hashPassword', 'verify'])
->shouldReceive('verify')
->with($user->password, 'wrong')
->andReturn(false);
Hash Length Mismatch:
VARCHAR(255) or TEXT for password fields.Algorithm Auto-Detection:
needsRehash() may fail if the hash format isn’t recognized (e.g., custom bcrypt parameters).HashUtil::checkPrefix() to explicitly check hash types before upgrading.Sodium/Argon2 Dependencies:
php-sodium). Argon2 needs php-argon2 (PECL).composer.json:
"require": {
"ext-sodium": "*",
"php-argon2": "^1.0"
}
Laravel’s Hash Facade Conflicts:
Hash facade can break third-party packages (e.g., Laravel Fortify).SymfonyHash) or conditional logic:
if (config('hash.use_symfony')) {
return $symfonyHasher->hashPassword($user, $password);
}
return Hash::make($password);
Memory Cost Tradeoffs:
memory_cost in Argon2 improves security but slows down authentication.memory_cost: 64 and monitor performance.Verify Hashes:
$hasher->verify($hash, 'wrong-password'); // Throws \Symfony\Component\PasswordHasher\Exception\InvalidArgumentException
Catch exceptions to handle invalid inputs gracefully.
Check Hash Format:
if (!HashUtil::checkPrefix($hash, ['bcrypt', 'argon2id'])) {
throw new \RuntimeException('Unsupported hash format');
}
Log Hashing Operations:
$hasher->hashPassword($user, $password, function ($hash) {
\Log::debug('Generated hash', ['hash' => substr($hash, 0, 10).'...']);
return $hash;
});
Custom Hashers:
Implement PasswordHasherInterface for bespoke algorithms:
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherInterface;
class CustomHasher implements PasswordHasherInterface
{
public function hash(string $plainPassword): string { /* ... */ }
public function verify(string $hashedPassword, string $plainPassword): bool { /* ... */ }
public function needsRehash(string $hashedPassword): bool { /* ... */ }
}
Register via PasswordHasherFactory:
$factory->addPasswordHasher('custom', new CustomHasher());
Hash Metadata:
Store algorithm parameters in the hash (e.g., argon2id$memory_cost=64$...):
$hasher->hashPassword($user, $password, ['memory_cost' => 128]);
Event-Driven Hashing: Trigger events on hash generation/verification:
$hasher->hashPassword($user, $password, function ($hash) {
event(new PasswordHashed($user, $hash));
});
Rate-Limited Hashing:
Use Symfony’s PasswordHasherInterface to integrate with Laravel’s rate-limiting middleware:
if (config('hash.rate_limit')) {
$this->middleware('throttle:hash,1')->only(['store']);
}
retrieved/saved events to auto-upgrade hashes:
User::retrieved(function ($user) {
$
How can I help you explore Laravel packages today?