From 0de1588a46ff2c0a30113948d5b456bc163deba6 Mon Sep 17 00:00:00 2001 From: Wouter J Date: Sun, 13 Oct 2019 01:38:54 +0200 Subject: [PATCH] Added article on password migrations --- security.rst | 1 + security/password_migration.rst | 210 ++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 security/password_migration.rst diff --git a/security.rst b/security.rst index c91f7babc2f..3bfb384121d 100644 --- a/security.rst +++ b/security.rst @@ -1000,6 +1000,7 @@ Authentication (Identifying/Logging in the User) security/form_login_setup security/json_login_setup security/guard_authentication + security/password_migration security/auth_providers security/user_provider security/ldap diff --git a/security/password_migration.rst b/security/password_migration.rst new file mode 100644 index 00000000000..7ce2d08d153 --- /dev/null +++ b/security/password_migration.rst @@ -0,0 +1,210 @@ +.. index:: + single: Security; How to Migrate a Password Hash + +How to Migrate a Password Hash +============================== + +.. versionadded:: 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 the +system, the user's password should be rehashed and stored. Symfony provides this +functionality when a user is successfully authenticated. + +To enable this, make sure you apply the following steps to your application: + +#. `Configure a new Encoder Using "migrate_from"`_ +#. `Upgrade the Password`_ +#. Optionally, `Trigger Password Migration From a Custom Encoder`_ + +Configure a new Encoder Using "migrate_from" +-------------------------------------------- + +When configuring a new encoder, you can specify a list of legacy encoders by +using the ``migrate_from`` option: + +.. configuration-block:: + + .. code-block:: yaml + + # config/packages/security.yaml + 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 + + .. code-block:: xml + + + + + + + + + + + + + + bcrypt + + + legacy + + + + + .. code-block:: php + + // 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 + ], + ], + ], + ]); + +.. tip:: + + The *auto*, *native*, *bcrypt* and *argon* encoders automatically enable + password migration using the following list of ``migrate_from`` algorithms: + + #. :ref:`PBKDF2 ` (which uses :phpfunction:`hash_pbkdf2`); + #. Message digest (which uses :phpfunction:`hash`) + + Both use the ``hash_algorithm`` setting as 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. You can enable this 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 secure as possible! + +Upgrade the Password when using Doctrine +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When using the :ref:`entity user provider `, implement +:class:`Symfony\\Component\\Security\\Core\\User\\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:: + + // 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($user); + } + } + +Upgrade the Password when using a Custom User Provider +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you're using a :ref:`custom user provider `, implement the +:class:`Symfony\\Component\\Security\\Core\\User\\PasswordUpgraderInterface` in +the user provider:: + + // 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:: + + // src/Security/UserProvider.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; + } + } + +.. _`the Doctrine docs for information`: https://www.doctrine-project.org/projects/doctrine-orm/en/current/reference/working-with-objects.html#custom-repositories