Symfony can verify an existing password with a legacy hasher and replace it with the current hash after a successful login. Configure both hashers and let the user provider persist the upgrade.
Last updated: October 17, 2026.
# config/packages/security.yaml
security:
password_hashers:
legacy_app:
id: App\Security\LegacyPasswordHasher
App\Entity\User:
algorithm: auto
migrate_from:
- legacy_appThe user class keeps the current hasher configuration. The named legacy service exists only so Symfony can recognize and verify stored older hashes.
Persist the replacement hash
Symfony’s password migration documentation requires the user provider or repository to implement PasswordUpgraderInterface. Its upgradePassword() method must save the new hash for the same user. A custom authenticator must use PasswordCredentials in its passport so the security system can perform the upgrade.
Do not implement PasswordHasherAwareInterface merely to return the legacy hasher name. With migrate_from, Symfony detects the legacy format and uses the current hasher for the replacement. A custom legacy hasher should implement verify() correctly and can return true from needsRehash() when its hashes are outdated.
Roll out migration without locking out users
Test three cases before deployment: a legacy hash accepts the correct password and is replaced; a current hash still authenticates without an unnecessary write; and an incorrect password never changes the database. Allocate enough space for future hashes—Symfony recommends a 255-character password column.
Make the upgrade write atomic and use the same user identifier that was authenticated. If two requests log in concurrently, both may attempt an upgrade; the final value should still be a valid current hash. Treat a persistence failure as an operational error rather than silently reporting that migration succeeded.
Keep the legacy verifier only while old hashes remain. Measure the remaining formats by inspecting safe hash prefixes or a separate migration marker, never by logging passwords. Accounts that never log in need a password-reset path; hashes cannot be converted without the original password. Remove the legacy hasher only after the remaining count reaches zero or an announced reset deadline passes.
Continue with secure PHP password hashing, modern authentication architecture, and generic login error handling.