Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Recordarme siempre y "signature_properties"

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 $12.00

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

Login Subscribe

Ahora que tenemos el sistema "recuérdame" funcionando, ¡juguemos con él! En lugar de dar al usuario la opción de activar "recuérdame", ¿podríamos... activarlo siempre?

En este caso, ya no necesitamos la casilla "Recuérdame", así que la eliminamos por completo.

always_remember_me: true

Hay dos formas de "forzar" al sistema remember me a establecer siempre una cookie aunque no esté la casilla de verificación. La primera es en security.yaml: establecer always_remember_me: en true:

security:
... lines 2 - 16
firewalls:
... lines 18 - 20
main:
... lines 22 - 27
remember_me:
... line 29
always_remember_me: true
... lines 31 - 43

Sí, acabo de escribir mal remember... ¡así que no lo hagas!

Con esto, nuestro autentificador sigue necesitando añadir un RememberMeBadge:

... lines 1 - 23
class LoginFormAuthenticator extends AbstractAuthenticator
{
... lines 26 - 39
public function authenticate(Request $request): PassportInterface
{
... lines 42 - 44
return new Passport(
... lines 46 - 55
new PasswordCredentials($password),
[
... lines 58 - 61
new RememberMeBadge(),
]
);
}
... lines 66 - 92
}

Pero el sistema ya no buscará esa casilla. Mientras vea esta insignia, añadirá la cookie.

Habilitación en el RememberMeBadge

La otra forma de habilitar la cookie "Recuérdame" en todas las situaciones es a través de la propia insignia. Comenta la nueva opción. Bueno... déjame arreglar mi error tipográfico y luego comentarlo:

security:
... lines 2 - 16
firewalls:
... lines 18 - 20
main:
... lines 22 - 27
remember_me:
... line 29
#always_remember_me: true
... lines 31 - 43

Dentro de LoginFormAuthenticator, en la propia insignia, puedes llamar a ->enable()... que devuelve la instancia de la insignia:

... lines 1 - 23
class LoginFormAuthenticator extends AbstractAuthenticator
{
... lines 26 - 39
public function authenticate(Request $request): PassportInterface
{
... lines 42 - 44
return new Passport(
... lines 46 - 55
new PasswordCredentials($password),
[
... lines 58 - 61
(new RememberMeBadge())->enable(),
]
);
}
... lines 66 - 92
}

Esto dice:

No me interesa ninguna otra configuración ni la casilla de verificación: Definitivamente quiero que el sistema remember me añada una cookie.

¡Vamos a probarlo! Borra la sesión y la cookie REMEMBERME. Esta vez, cuando iniciemos la sesión... ¡oh, token CSRF no válido! Eso es porque acabo de matar mi sesión sin refrescar - ¡tonto Ryan! Refresca e inténtalo de nuevo.

¡Muy bien! ¡Tenemos la cookie REMEMBERME!

Asegurar las cookies Remember Me: Invalidar al cambiar los datos del usuario

Hay una cosa con la que debes tener cuidado cuando se trata de las cookies "Recuérdame". Si un usuario malintencionado consiguiera de algún modo acceder a mi cuenta -por ejemplo, si robara mi contraseña-, podría, por supuesto, iniciar la sesión. Normalmente, eso es un asco... pero en cuanto lo descubra, podría cambiar mi contraseña, lo que les desconectaría.

Pero... si ese mal usuario tiene una cookie de REMEMEBERME... entonces, aunque cambie mi contraseña, seguirá conectado hasta que esa cookie caduque... lo que podría ser dentro de mucho tiempo. Estas cookies son casi tan buenas como las reales: actúan como "billetes de autentificación gratuitos". Y siguen funcionando -independientemente de lo que hagamos- hasta que caducan.

Afortunadamente, en el nuevo sistema de autenticación, hay una forma muy interesante de evitar esto. En security.yaml, debajo de remember_me, añade una nueva opción llamadasignature_properties configurada en un array con password dentro:

security:
... lines 2 - 16
firewalls:
... lines 18 - 20
main:
... lines 22 - 27
remember_me:
... line 29
signature_properties: [password]
... lines 31 - 44

Me explico. Cuando Symfony crea la cookie remember me, crea una "firma" que demuestra que esta cookie es válida. Gracias a esta configuración, ahora obtendrá la propiedadpassword de nuestro User y la incluirá en la firma. Luego, cuando esa cookie se utilice para autenticarse, Symfony volverá a crear la firma utilizando el password del User que está actualmente en la base de datos y se asegurará de que las dos firmas coincidan. Así que si el password de la base de datos es diferente a la contraseña que se utilizó para crear originalmente la cookie... ¡la coincidencia de la firma fallará!

En otras palabras, para cualquier propiedad de esta lista, si incluso una de estas cambia en la base de datos en ese User, todas las cookies "recuérdame" para ese usuario serán invalidadas instantáneamente.

Así que si un usuario malo me roba la cuenta, todo lo que tengo que hacer es cambiar mi contraseña y ese usuario malo será expulsado.

Esto es superguay verlo en acción. Actualiza la página. Si modificas la configuración designature_properties, se invalidarán todas las cookies de REMEMBERME en todo el sistema: así que asegúrate de que la configuración es correcta cuando lo configures por primera vez. Observa: si borro la cookie de sesión y actualizo... ¡sí! No estoy autentificado: la cookie de REMEMBERME no ha funcionado. Sigue ahí... pero no es funcional.

Iniciemos la sesión - con nuestra dirección de correo electrónico normal... y la contraseña... para que obtengamos una nueva cookie remember me que se crea con la contraseña con hash.

¡Genial! Y ahora, en condiciones normales, las cosas funcionarán como siempre. Puedo borrar la cookie de sesión, actualizarla y seguiré conectado.

Pero ahora, vamos a cambiar la contraseña del usuario en la base de datos. Podemos hacer trampa y hacer esto en la línea de comandos:

symfony console doctrine:query:sql 'UPDATE user SET password="foo" WHERE email = "abraca_admin@example.com"'

Poner la contraseña en foo es una auténtica tontería... ya que esta columna debe contener una contraseña con hash... pero estará bien para nuestros propósitos. Pulsa y... ¡fantástico! Esto imita lo que ocurriría si cambiara la contraseña de mi cuenta.

Ahora, si somos el usuario malo, la próxima vez que volvamos al sitio... ¡de repente habremos cerrado la sesión! ¡Una barbaridad! ¡Y yo también me habría salido con la mía si no fuera por vosotros, niños entrometidos! La cookie "recuérdame" está ahí... pero no funciona. Me encanta esta función.

Volvamos atrás... y recarguemos nuestras instalaciones para arreglar mi contraseña:

symfony console doctrine:fixtures:load

Y... una vez hecho esto, vuelve a conectarte como abraca_admin@example.com, contraseña tada.

A continuación: ¡es hora de tener un viaje de poder y empezar a negar el acceso! Veamosaccess_control: la forma más sencilla de bloquear el acceso a secciones enteras de tu sitio.

Leave a comment!

¡Este tutorial también funciona muy bien para Symfony 6!

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": "^7.4.1 || ^8.0.0",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "babdev/pagerfanta-bundle": "^3.3", // v3.3.0
        "composer/package-versions-deprecated": "^1.11", // 1.11.99.4
        "doctrine/annotations": "^1.0", // 1.13.2
        "doctrine/doctrine-bundle": "^2.1", // 2.6.3
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.1.1
        "doctrine/orm": "^2.7", // 2.10.1
        "knplabs/knp-markdown-bundle": "^1.8", // 1.9.0
        "knplabs/knp-time-bundle": "^1.11", // v1.16.1
        "pagerfanta/doctrine-orm-adapter": "^3.3", // v3.3.0
        "pagerfanta/twig": "^3.3", // v3.3.0
        "phpdocumentor/reflection-docblock": "^5.2", // 5.2.2
        "scheb/2fa-bundle": "^5.12", // v5.12.1
        "scheb/2fa-qr-code": "^5.12", // v5.12.1
        "scheb/2fa-totp": "^5.12", // v5.12.1
        "sensio/framework-extra-bundle": "^6.0", // v6.2.0
        "stof/doctrine-extensions-bundle": "^1.4", // v1.6.0
        "symfony/asset": "5.3.*", // v5.3.4
        "symfony/console": "5.3.*", // v5.3.7
        "symfony/dotenv": "5.3.*", // v5.3.8
        "symfony/flex": "^1.3.1", // v1.17.5
        "symfony/form": "5.3.*", // v5.3.8
        "symfony/framework-bundle": "5.3.*", // v5.3.8
        "symfony/monolog-bundle": "^3.0", // v3.7.0
        "symfony/property-access": "5.3.*", // v5.3.8
        "symfony/property-info": "5.3.*", // v5.3.8
        "symfony/rate-limiter": "5.3.*", // v5.3.4
        "symfony/runtime": "5.3.*", // v5.3.4
        "symfony/security-bundle": "5.3.*", // v5.3.8
        "symfony/serializer": "5.3.*", // v5.3.8
        "symfony/stopwatch": "5.3.*", // v5.3.4
        "symfony/twig-bundle": "5.3.*", // v5.3.4
        "symfony/ux-chartjs": "^1.3", // v1.3.0
        "symfony/validator": "5.3.*", // v5.3.8
        "symfony/webpack-encore-bundle": "^1.7", // v1.12.0
        "symfony/yaml": "5.3.*", // v5.3.6
        "symfonycasts/verify-email-bundle": "^1.5", // v1.5.0
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.3
        "twig/string-extra": "^3.3", // v3.3.3
        "twig/twig": "^2.12|^3.0" // v3.3.3
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.0
        "symfony/debug-bundle": "5.3.*", // v5.3.4
        "symfony/maker-bundle": "^1.15", // v1.34.0
        "symfony/var-dumper": "5.3.*", // v5.3.8
        "symfony/web-profiler-bundle": "5.3.*", // v5.3.8
        "zenstruck/foundry": "^1.1" // v1.13.3
    }
}