Symfony
sponsored by SensioLabs
Menu
  • About
  • Documentation
  • Screencasts
  • Cloud
  • Certification
  • Community
  • Businesses
  • News
  • Download
  1. Home
  2. Documentation
  3. Security
  4. How to Migrate a Password Hash
  • Documentation
  • Book
  • Reference
  • Bundles
  • Cloud
Search by Algolia

Table of Contents

  • Configure a new Encoder Using "migrate_from"
  • Upgrade the Password
    • Provide the Password when using Guard
    • Upgrade the Password when using Doctrine
    • Upgrade the Password when using a Custom User Provider
  • Trigger Password Migration From a Custom Encoder

How to Migrate a Password Hash

Edit this page

Warning: You are browsing the documentation for Symfony 4.4, which is no longer maintained.

Read the updated version of this page for Symfony 6.2 (the current stable version).

How to Migrate a Password Hash

4.4

Password migration was introduced in Symfony 4.4.

In order to protect passwords, it is recommended to store them using the latest hash algorithms. This means that if a better hash algorithm is supported on your system, the user's password should be rehashed using the newer algorithm and stored. That's possible with the migrate_from option:

  1. Configure a new Encoder Using "migrate_from"
  2. Upgrade the Password
  3. Optionally, Trigger Password Migration From a Custom Encoder

Configure a new Encoder Using "migrate_from"

When a better hashing algorithm becomes available, you should keep the existing encoder(s), rename it, and then define the new one. Set the migrate_from option on the new encoder to point to the old, legacy encoder(s):

  • YAML
  • XML
  • PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# config/packages/security.yaml
security:
    # ...

    encoders:
        # an encoder used in the past for some users
        legacy:
            algorithm: sha256
            encode_as_base64: false
            iterations: 1

        App\Entity\User:
            # the new encoder, along with its options
            algorithm: sodium
            migrate_from:
                - bcrypt # uses the "bcrypt" encoder with the default options
                - legacy # uses the "legacy" encoder configured above
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- config/packages/security.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:security="http://symfony.com/schema/dic/security"
    xsi:schemaLocation="http://symfony.com/schema/dic/security
        https://symfony.com/schema/dic/security/security-1.0.xsd">

    <security:config>
        <!-- ... -->

        <security:encoder class="legacy"
            algorithm="sha256"
            encode-as-base64="false"
            iterations="1"
        />

        <!-- algorithm: the new encoder, along with its options -->
        <security:encoder class="App\Entity\User"
            algorithm="sodium"
        >
            <!-- uses the bcrypt encoder with the default options -->
            <security:migrate-from>bcrypt</security:migrate-from>

            <!-- uses the legacy encoder configured above -->
            <security:migrate-from>legacy</security:migrate-from>
        </security:encoder>
    </security:config>
</container>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// config/packages/security.php
$container->loadFromExtension('security', [
    // ...

    'encoders' => [
        'legacy' => [
            'algorithm' => 'sha256',
            'encode_as_base64' => false,
            'iterations' => 1,
        ],

        'App\Entity\User' => [
            // the new encoder, along with its options
            'algorithm' => 'sodium',
            'migrate_from' => [
                'bcrypt', // uses the "bcrypt" encoder with the default options
                'legacy', // uses the "legacy" encoder configured above
            ],
        ],
    ],
]);

With this setup:

  • New users will be encoded with the new algorithm;
  • Whenever a user logs in whose password is still stored using the old algorithm, Symfony will verify the password with the old algorithm and then rehash and update the password using the new algorithm.

Tip

The auto, native, bcrypt and argon encoders automatically enable password migration using the following list of migrate_from algorithms:

  1. PBKDF2 (which uses hash_pbkdf2);
  2. Message digest (which uses hash)

Both use the hash_algorithm setting as the algorithm. It is recommended to use migrate_from instead of hash_algorithm, unless the auto encoder is used.

Upgrade the Password

Upon successful login, the Security system checks whether a better algorithm is available to hash the user's password. If it is, it'll hash the correct password using the new hash. If you use a Guard authenticator, you first need to provide the original password to the Security system.

You can enable the upgrade behavior by implementing how this newly hashed password should be stored:

  • When using Doctrine's entity user provider
  • When using a custom user provider

After this, you're done and passwords are always hashed as securely as possible!

Provide the Password when using Guard

When you're using a custom guard authenticator, you need to implement PasswordAuthenticatedInterface. This interface defines a getPassword() method that returns the password for this login request. This password is used in the migration process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Security/CustomAuthenticator.php
namespace App\Security;

use Symfony\Component\Security\Guard\PasswordAuthenticatedInterface;
// ...

class CustomAuthenticator extends AbstractGuardAuthenticator implements PasswordAuthenticatedInterface
{
    // ...

    public function getPassword($credentials): ?string
    {
        return $credentials['password'];
    }
}

Upgrade the Password when using Doctrine

When using the entity user provider, implement PasswordUpgraderInterface in the UserRepository (see the Doctrine docs for information on how to create this class if it's not already created). This interface implements storing the newly created password hash:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Repository/UserRepository.php
namespace App\Repository;

// ...
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;

class UserRepository extends EntityRepository implements PasswordUpgraderInterface
{
    // ...

    public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
    {
        // set the new encoded password on the User object
        $user->setPassword($newEncodedPassword);

        // execute the queries on the database
        $this->getEntityManager()->flush();
    }
}

Upgrade the Password when using a Custom User Provider

If you're using a custom user provider, implement the PasswordUpgraderInterface in the user provider:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Security/UserProvider.php
namespace App\Security;

// ...
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;

class UserProvider implements UserProviderInterface, PasswordUpgraderInterface
{
    // ...

    public function upgradePassword(UserInterface $user, string $newEncodedPassword): void
    {
        // set the new encoded password on the User object
        $user->setPassword($newEncodedPassword);

        // ... store the new password
    }
}

Trigger Password Migration From a Custom Encoder

If you're using a custom password encoder, you can trigger the password migration by returning true in the needsRehash() method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Security/CustomPasswordEncoder.php
namespace App\Security;

// ...
use Symfony\Component\Security\Core\Encoder\PasswordEncoderInterface;

class CustomPasswordEncoder implements PasswordEncoderInterface
{
    // ...

    public function needsRehash(string $encoded): bool
    {
        // check whether the current password is hash using an outdated encoder
        $hashIsOutdated = ...;

        return $hashIsOutdated;
    }
}
This work, including the code samples, is licensed under a Creative Commons BY-SA 3.0 license.
We stand with Ukraine.
Version:
Measure & Improve Symfony Code Performance

Measure & Improve Symfony Code Performance

Peruse our complete Symfony & PHP solutions catalog for your web development needs.

Peruse our complete Symfony & PHP solutions catalog for your web development needs.

↓ Our footer now uses the colors of the Ukrainian flag because Symfony stands with the people of Ukraine.

Avatar of Joe Springe, a Symfony contributor

Thanks Joe Springe for being a Symfony contributor

2 commits • 20 lines changed

View all contributors that help us make Symfony

Become a Symfony contributor

Be an active part of the community and contribute ideas, code and bug fixes. Both experts and newcomers are welcome.

Learn how to contribute

Symfony™ is a trademark of Symfony SAS. All rights reserved.

  • What is Symfony?
    • Symfony at a Glance
    • Symfony Components
    • Case Studies
    • Symfony Releases
    • Security Policy
    • Logo & Screenshots
    • Trademark & Licenses
    • symfony1 Legacy
  • Learn Symfony
    • Symfony Docs
    • Symfony Book
    • Reference
    • Bundles
    • Best Practices
    • Training
    • eLearning Platform
    • Certification
  • Screencasts
    • Learn Symfony
    • Learn PHP
    • Learn JavaScript
    • Learn Drupal
    • Learn RESTful APIs
  • Community
    • SymfonyConnect
    • Support
    • How to be Involved
    • Code of Conduct
    • Events & Meetups
    • Projects using Symfony
    • Downloads Stats
    • Contributors
    • Backers
  • Blog
    • Events & Meetups
    • A week of symfony
    • Case studies
    • Cloud
    • Community
    • Conferences
    • Diversity
    • Documentation
    • Living on the edge
    • Releases
    • Security Advisories
    • SymfonyInsight
    • Twig
    • SensioLabs
  • Services
    • SensioLabs services
    • Train developers
    • Manage your project quality
    • Improve your project performance
    • Host Symfony projects
    Deployed on
Follow Symfony
Search by Algolia