Skip to content

[Security] Renamed encoders to password_hashers #15307

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions _build/redirection_map
Original file line number Diff line number Diff line change
Expand Up @@ -514,3 +514,4 @@
/service_container/3.3-di-changes https://symfony.com/doc/3.4/service_container/3.3-di-changes.html
/frontend/encore/shared-entry /frontend/encore/split-chunks
/testing/functional_tests_assertions /testing#testing-application-assertions
/security/named_encoders /security/named_hashers
105 changes: 55 additions & 50 deletions components/security/authentication.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,12 @@ the given password is valid.

This functionality is offered by the :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`.
It fetches the user's data from a :class:`Symfony\\Component\\Security\\Core\\User\\UserProviderInterface`,
uses a :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
uses a :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface`
to create a hash of the password and returns an authenticated token if the
password was valid::

use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactoryInterface;
use Symfony\Component\Security\Core\Authentication\Provider\DaoAuthenticationProvider;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\User\InMemoryUserProvider;
use Symfony\Component\Security\Core\User\UserChecker;

Expand All @@ -145,14 +145,14 @@ password was valid::
// for some extra checks: is account enabled, locked, expired, etc.
$userChecker = new UserChecker();

// an array of password encoders (see below)
$encoderFactory = new EncoderFactory(...);
// an array of password hashers (see below)
$hasherFactory = new PasswordHasherFactoryInterface(...);

$daoProvider = new DaoAuthenticationProvider(
$userProvider,
$userChecker,
'secured_area',
$encoderFactory
$hasherFactory
);

$daoProvider->authenticate($unauthenticatedToken);
Expand All @@ -165,96 +165,105 @@ password was valid::
It is also possible to let multiple user providers try to find the user's
data, using the :class:`Symfony\\Component\\Security\\Core\\User\\ChainUserProvider`.

The Password Encoder Factory
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. _the-password-encoder-factory:

The Password Hasher Factory
~~~~~~~~~~~~~~~~~~~~~~~~~~~

The :class:`Symfony\\Component\\Security\\Core\\Authentication\\Provider\\DaoAuthenticationProvider`
uses an encoder factory to create a password encoder for a given type of
user. This allows you to use different encoding strategies for different
types of users. The default :class:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory`
receives an array of encoders::
uses a factory to create a password hasher for a given type of user. This allows
you to use different hashing strategies for different types of users.
The default :class:`Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactory`
receives an array of hashers::

use Acme\Entity\LegacyUser;
use Symfony\Component\Security\Core\Encoder\EncoderFactory;
use Symfony\Component\Security\Core\Encoder\MessageDigestPasswordEncoder;
use Symfony\Component\PasswordHasher\Hasher\MessageDigestPasswordHasher;
use Symfony\Component\PasswordHasher\Hasher\PasswordHasherFactory;
use Symfony\Component\Security\Core\User\InMemoryUser;

$defaultEncoder = new MessageDigestPasswordEncoder('sha512', true, 5000);
$weakEncoder = new MessageDigestPasswordEncoder('md5', true, 1);
$defaultHasher = new MessageDigestPasswordHasher('sha512', true, 5000);
$weakHasher = new MessageDigestPasswordHasher('md5', true, 1);

$encoders = [
InMemoryUser::class => $defaultEncoder,
LegacyUser::class => $weakEncoder,
$hashers = [
InMemoryUser::class => $defaultHasher,
LegacyUser::class => $weakHasher,
// ...
];
$encoderFactory = new EncoderFactory($encoders);
$hasherFactory = new PasswordHasherFactory($hashers);

Each encoder should implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
Each hasher should implement :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface`
or be an array with a ``class`` and an ``arguments`` key, which allows the
encoder factory to construct the encoder only when it is needed.
hasher factory to construct the hasher only when it is needed.

.. _creating-a-custom-password-encoder:

Creating a custom Password Encoder
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Creating a custom Password Hasher
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

There are many built-in password encoders. But if you need to create your
There are many built-in password hasher. But if you need to create your
own, it needs to follow these rules:

#. The class must implement :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
(you can also extend :class:`Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder`);
#. The class must implement :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface`
(you can also extend :class:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasher`);

#. The implementations of
:method:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface::encodePassword`
:method:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface::hashPassword`
and
:method:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface::isPasswordValid`
:method:`Symfony\\Component\\PasswordHasher\\Hasher\\UserPasswordHasherInterface::isPasswordValid`
must first of all make sure the password is not too long, i.e. the password length is no longer
than 4096 characters. This is for security reasons (see `CVE-2013-5750`_), and you can use the
:method:`Symfony\\Component\\Security\\Core\\Encoder\\BasePasswordEncoder::isPasswordTooLong`
:method:`Symfony\\Component\\PasswordHasher\\Hasher\\CheckPasswordLengthTrait::isPasswordTooLong`
method for this check::

use Symfony\Component\Security\Core\Encoder\BasePasswordEncoder;
use Symfony\Component\PasswordHasher\Hasher\CheckPasswordLengthTrait;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasher;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;

class FoobarEncoder extends BasePasswordEncoder
class FoobarHasher extends UserPasswordHasher
{
public function encodePassword($raw, $salt)
use CheckPasswordLengthTrait;

public function hashPassword(UserInterface $user, string $plainPassword): string
{
if ($this->isPasswordTooLong($raw)) {
if ($this->isPasswordTooLong($user->getPassword())) {
throw new BadCredentialsException('Invalid password.');
}

// ...
}

public function isPasswordValid($encoded, $raw, $salt)
public function isPasswordValid(UserInterface $user, string $plainPassword)
{
if ($this->isPasswordTooLong($raw)) {
if ($this->isPasswordTooLong($user->getPassword())) {
return false;
}

// ...
}
}

Using Password Encoders
~~~~~~~~~~~~~~~~~~~~~~~
.. _using-password-encoders:

When the :method:`Symfony\\Component\\Security\\Core\\Encoder\\EncoderFactory::getEncoder`
method of the password encoder factory is called with the user object as
its first argument, it will return an encoder of type :class:`Symfony\\Component\\Security\\Core\\Encoder\\PasswordEncoderInterface`
which should be used to encode this user's password::
Using Password Hashers
~~~~~~~~~~~~~~~~~~~~~~

When the :method:`Symfony\\Component\\PasswordHasher\\Hasher\\PasswordHasherFactory::getPasswordHasher`
method of the password hasher factory is called with the user object as
its first argument, it will return a hasher of type :class:`Symfony\\Component\\PasswordHasher\\PasswordHasherInterface`
which should be used to hash this user's password::

// a Acme\Entity\LegacyUser instance
$user = ...;

// the password that was submitted, e.g. when registering
$plainPassword = ...;

$encoder = $encoderFactory->getEncoder($user);
$hasher = $hasherFactory->getPasswordHasher($user);

// returns $weakEncoder (see above)
$encodedPassword = $encoder->encodePassword($plainPassword, $user->getSalt());
// returns $weakHasher (see above)
$hashedPassword = $hasher->hashPassword($user, $plainPassword);

$user->setPassword($encodedPassword);
$user->setPassword($hashedPassword);

// ... save the user

Expand All @@ -267,11 +276,7 @@ in) is correct, you can use::
// the submitted password, e.g. from the login form
$plainPassword = ...;

$validPassword = $encoder->isPasswordValid(
$user->getPassword(), // the encoded password
$plainPassword, // the submitted password
$user->getSalt()
);
$validPassword = $hasher->isPasswordValid($user, $plainPassword);

Authentication Events
---------------------
Expand Down
Loading