Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Codificadores de contraseña -> password_hashers & debug:firewall

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $10.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Al convertirnos al nuevo sistema de seguridad, nuestras desaprobaciones acaban de bajar. Si miras lo que queda, una de ellas dice

El nodo hijo "encoders" en la ruta "security" está obsoleto, utiliza "password_hashers" en su lugar.

Este es otro cambio que vimos al actualizar la receta de security-bundle. Originalmente, teníamos encoders. Esto le dice a Symfony qué algoritmo usar para hacer el hash de la contraseña. Esto ha sido renombrado a password_hashers. Y en lugar de necesitar nuestra clase personalizada, siempre podemos usar esta configuración. Esto dice

Cualquier clase que implemente PasswordAuthenticatedUserInterface debe utilizar el algoritmo auto.

Y como toda clase de usuario con contraseña lo implementará -incluida nuestra clase- esto nos cubre.

Si antes tenías un algoritmo diferente, muévelo a esta línea. No queremos cambiar de algoritmo, sólo eliminar encoders en favor depassword_hashers.

Ahora, en la página de inicio... ¡tenemos aún menos depreciaciones! ¡Quedan dos! Intentemos conectarnos. ¡Ah! Creo que antes se me escaparon algunos conflictos en mi diseño base.

Vamos a pasar por encima de ellos y a arreglarlos. En templates/base.html.twig... sí. Cuando actualizamos la receta de twig-bundle, esto entraba en conflicto y no me di cuenta.

Ahora... mucho mejor. Iniciemos la sesión: tenemos un usuario llamado abraca_admin@example.comcon contraseña tada. Entra y... ¡está vivo!

El comando debug:firewall

Por cierto, hablando de "seguridad" y "cortafuegos", Symfony incluye un nuevo comando para ayudar a depurar y visualizar tu cortafuegos. Se llama, apropiadamente,debug:firewall. Si lo ejecutas sin argumentos:

php bin/console debug:firewall

Te dirá los nombres de tu cortafuegos: dev y main. Vuelve a ejecutarlo con main:

php bin/console debug:firewall main

¡Aquí lo tenemos! Esto nos dice qué autentificadores tiene este cortafuegos, qué proveedor de usuarios está utilizando -aunque nuestra aplicación normalmente sólo tiene uno- y también el punto de entrada, que es algo de lo que hablamos en nuestro tutorial de Seguridad.

Bien, pon una gran marca de verificación junto a "actualizar la seguridad". A continuación, vamos a machacar las últimas depreciaciones y a aprender cómo podemos estar seguros de que no se nos ha escapado ninguna.

Leave a comment!

6
Login or Register to join the conversation
Artur-M Avatar
Artur-M Avatar Artur-M | posted hace 5 meses | edited

Hello,
I upgraded my project to Symfony 6 and new authentication system using this course. For sure It works, but I have only one concern and cannot make it. In my old guard authenticator system (coming from Symfony 4.3) password are hashed using argon2id (those start with eg.: $argon2id$v=19$m=65536,t=4,p=1$pXf....) I am not sure if that hashing algorithm is good to use now, but I understood from your course and Symfony manual pages that current bcrypt (so 'auto' here) is better. So I tried to make it automatically migrated.
To my User class I added implementing PasswordUpgraderInterface and upgrade password method:

    public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
    {
        $this->setPassword($newHashedPassword);
    }

I set in security.yaml (I am totally not sure which one I should set in migrate_from key, so I set many :-) - but also tried each single):

enable_authenticator_manager: true
    password_hashers:
        # auto hasher with custom options for all PasswordAuthenticatedUserInterface instances
        Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
            algorithm: 'auto'
            cost: 16
            migrate_from:
                - sodium
                - argon2
                - argon2i
                - argon2d
                - argon2id

Anyway, it does not seem to work. Newly created accounts have new password hashes (eg. starting with: $2y$13$T7h6DMcZsEyE2LAuK3l...), but when logging onto account which was created before this change password hash is not upgraded to new one. Although I tried to use manual from: https://symfony.com/doc/current/security/passwords.html#upgrade-the-password, I clearly see I messed something :-).
Would be great if you could hint me something.
Best
Artur

Reply

Hey Artur!

Sorry for the slow reply! Yes, this is complex stuff! The first question I'm wondering is (and I'm not sure of the answer): is bcrypt actually considered that needs to be rehashed or not? It may not be the preferred hashing algorithm, but it's quite likely it's still considered good enough (that may change in the future at some point, but then Symfony WOULD start re-hashing it automatically at that time).

If I'm reading the code correctly, here is how to check:

When you log in via a user that users bcrypt, this method should be called: https://github.com/symfony/symfony/blob/ddaedd28bd2e4fc22b64567753cf934fe9d68c4c/src/Symfony/Component/PasswordHasher/Hasher/NativePasswordHasher.php#L113-L116 - I would add a dd(password_needs_rehash($hashedPassword, $this->algorithm, $this->options);) before the return statement. Then, log in, and see if it hits. And if so, does it return true or not? My guess is that it will return false meaning that the passwords are fine and don't need to be rehashed.

Let me know if you find anything out!

Cheers!

Reply
Artur-M Avatar
Artur-M Avatar Artur-M | weaverryan | posted hace 4 meses | edited

Many thanks for your response.
Unfortunately:

NativePasswordHasher.php on line 118:
true

I am not sure if I understand PasswordUpgraderInterface correctly - and what work/logic should be done in this method (upgradePassword). Because I put it in User entity used for security system I thought I can just set newHashedPassword to user entity, I also tried to

public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
    $user->setPassword($newHashedPassword); // instead of $this->setPassword($newHashedPassword);
}

but it does nothing as this method is not called anyway during login.
I only consider how to persist this object as this is already entity. Maybe I should create separate class for PasswordUpgradeInterface implementation?
From what I see in method description:
**

 * Upgrades the hashed password of a user, typically for using a better hash algorithm.
 *
 * This method should persist the new password in the user storage and update the $user object accordingly.
 * Because you don't want your users not being able to log in, this method should be opportunistic:
 * it's fine if it does nothing or if it fails without throwing any exception.
 */
Reply

Hey @Artur-M!

Ok, this gets us much closer then :). So it seems that the problem now is that we know the password should be upgraded, but that the upgradePassword() is not being called. And, I'm sorry, I should have caught this detail earlier, but this method (and the PasswordUpgraderInterface) should not live on the User class but on UserRepository: https://symfony.com/doc/5.2/security/password_migration.html#upgrade-the-password-when-using-doctrine - try moving it there and don't forget to include the ->flush() call.

In case you're curious, or it's helpful, here is the logic that's called during login to trigger this entire flow - https://github.com/symfony/symfony/blob/683eeb3890676e617cb1b5819757b9cc8e6b4871/src/Symfony/Component/Security/Http/EventListener/PasswordMigratingListener.php#L57-L86 - you can add debugging statements there if needed to see what's not working :).

Cheers!

Reply
Artur-M Avatar
Artur-M Avatar Artur-M | weaverryan | posted hace 4 meses | edited

Yep, I already spotted that it should live in UserRepository - because after trying many things in User.php class I noted I already have it there, using this course as tutor :-) Anyway, I see this method is not called (when I try to dd or dump inside). So now I tried to check what happens in PasswordUpgradeListener.php and it seems $userLoader and badge here does not have passwordUpgrader properly set so it returns without upgrading password in line 77. First checked with dd that $userLoader is not array, it is Closure so after line 71 I set:
dd($userLoader instanceof \Closure, ((new \ReflectionFunction($userLoader))->getClosureThis()) instanceof PasswordUpgraderInterface);
and get true and false, so it return too quickly...
When I checked that $userLoader is Closure with class LoginFormAuthenticator it seems this should contain upgradePassword method implemented, not in Doctrine Repository. And after implementing it in CustomFormAuthenticator it started work eventually!
Many thanks for help and directing me! I always afraid of touching/modifying source code, but anyway I really never would be able to find this listener and do that investigation without your replies.

Reply

Hey @Artur-M!

GREAT debugging! I'm so happy you got it figured out and working!

Cheers!

Reply
Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.6", // v3.6.1
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.5
        "doctrine/annotations": "^1.13", // 1.13.2
        "doctrine/dbal": "^3.3", // 3.3.5
        "doctrine/doctrine-bundle": "^2.0", // 2.6.2
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.0", // 2.11.2
        "knplabs/knp-markdown-bundle": "^1.8", // 1.10.0
        "knplabs/knp-time-bundle": "^1.18", // v1.18.0
        "pagerfanta/doctrine-orm-adapter": "^3.6", // v3.6.1
        "pagerfanta/twig": "^3.6", // v3.6.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.6
        "sentry/sentry-symfony": "^4.0", // 4.2.8
        "stof/doctrine-extensions-bundle": "^1.5", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.7
        "symfony/console": "6.0.*", // v6.0.7
        "symfony/dotenv": "6.0.*", // v6.0.5
        "symfony/flex": "^2.1", // v2.1.7
        "symfony/form": "6.0.*", // v6.0.7
        "symfony/framework-bundle": "6.0.*", // v6.0.7
        "symfony/mailer": "6.0.*", // v6.0.5
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/property-access": "6.0.*", // v6.0.7
        "symfony/property-info": "6.0.*", // v6.0.7
        "symfony/proxy-manager-bridge": "6.0.*", // v6.0.6
        "symfony/routing": "6.0.*", // v6.0.5
        "symfony/runtime": "6.0.*", // v6.0.7
        "symfony/security-bundle": "6.0.*", // v6.0.5
        "symfony/serializer": "6.0.*", // v6.0.7
        "symfony/stopwatch": "6.0.*", // v6.0.5
        "symfony/twig-bundle": "6.0.*", // v6.0.3
        "symfony/ux-chartjs": "^2.0", // v2.1.0
        "symfony/validator": "6.0.*", // v6.0.7
        "symfony/webpack-encore-bundle": "^1.7", // v1.14.0
        "symfony/yaml": "6.0.*", // v6.0.3
        "symfonycasts/verify-email-bundle": "^1.7", // v1.10.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.8
        "twig/string-extra": "^3.3", // v3.3.5
        "twig/twig": "^2.12|^3.0" // v3.3.10
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.1
        "phpunit/phpunit": "^9.5", // 9.5.20
        "rector/rector": "^0.12.17", // 0.12.20
        "symfony/debug-bundle": "6.0.*", // v6.0.3
        "symfony/maker-bundle": "^1.15", // v1.38.0
        "symfony/var-dumper": "6.0.*", // v6.0.6
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.6
        "zenstruck/foundry": "^1.16" // v1.18.0
    }
}