Password Hasher
The Password Hasher plugin provides secure password hashing and verification using industry-standard algorithms.
Four adapters are available:
- Argon2idPasswordHasher uses Argon2id, recommended by OWASP 2024 for new applications.
- Argon2iPasswordHasher uses Argon2i, optimized for side-channel attack resistance.
- BcryptPasswordHasher uses bcrypt, a proven algorithm still widely supported.
- PlaintextPasswordHasher produces predictable hashes for testing (never use in production).
Why Use It
Passwords must never be stored in plain text. The Password Hasher ensures secure hashing with automatic salt generation and timing-safe verification. The test adapter enables predictable fixtures and assertions.
Usage
Hash passwords before storage:
Verify passwords during authentication:
Argon2id (Recommended)
Argon2id is memory-hard, protecting against GPU and ASIC attacks:
$hasher = new Argon2idPasswordHasher(
memoryCost: 65536, // 64 MB (default)
timeCost: 4, // iterations (default)
threads: 1, // parallelism (default)
);
// Hash format: $argon2id$v=19$m=65536,t=4,p=1$...
Argon2i
Argon2i is optimized for resistance to side-channel attacks:
$hasher = new Argon2iPasswordHasher(
memoryCost: 65536, // 64 MB (default)
timeCost: 4, // iterations (default)
threads: 1, // parallelism (default)
);
// Hash format: $argon2i$v=19$m=65536,t=4,p=1$...
Bcrypt
Bcrypt remains a solid choice for compatibility with existing systems:
Algorithm Comparison
| Aspect | Argon2id | Argon2i | Bcrypt |
|---|---|---|---|
| OWASP 2024 | Recommended | Acceptable | Acceptable |
| Memory-hard | Yes | Yes | No |
| GPU resistance | High | High | Moderate |
| Side-channel resistance | Moderate | High | Low |
| PHP support | 7.3+ | 7.2+ | 5.5+ |
Testing
The PlaintextPasswordHasher produces predictable hashes:
$hasher = new PlaintextPasswordHasher();
$hash = $hasher->hash('secret123'); // Returns: $plain$secret123
$hasher->verify('secret123', '$plain$secret123'); // true
This enables fixtures with known password hashes and predictable test assertions.
Architecture Pattern
The demo application separates password concerns into two Value Objects:
- Password: Validates plaintext input (length, complexity rules)
- HashedPassword: Stores the hashed value from the database
Password hashing and verification happen in the Application layer with explicit dependency injection:
final class AuthenticateUserHandler extends AbstractCommandHandler
{
public function __construct(
private readonly UserRepository $userRepository,
private readonly PasswordHasherInterface $passwordHasher,
// ...
) {}
public function handle(CommandInterface $command): void
{
$user = $this->userRepository->findByEmail($email);
if (!$this->passwordHasher->verify($command->password, $user->getHashedPassword()->getValue())) {
throw new InvalidArgumentException('Invalid credentials');
}
// ...
}
}
This approach follows Clean Architecture principles:
| Aspect | Benefit |
|---|---|
| Explicit dependencies | Visible in constructor, easy to test |
| No global state | No static registry or service locator |
| Domain purity | Value Objects have no service dependencies |
| Testability | Inject PlaintextPasswordHasher for predictable test behavior |
Source Files
app/demo/User/Domain/Password.php- Plaintext validationapp/demo/User/Domain/HashedPassword.php- Stored hash wrapperapp/demo/User/Application/Command/AuthenticateUserHandler.php- Verification logic
See Also
- Authentication - Password verification in auth flow
- Value Objects - Password as Value Object pattern