Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Las cadenas especiales ISAUTHENTICATED

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

Si simplemente necesitamos averiguar si el usuario está conectado o no, buscamos en ROLE_USER:

<!DOCTYPE html>
<html>
... lines 3 - 14
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-1">
<div class="container-fluid">
... lines 18 - 26
<div class="collapse navbar-collapse" id="navbar-collapsable">
... lines 28 - 38
{% if is_granted('ROLE_USER') %}
... lines 40 - 43
{% endif %}
</div>
</div>
</nav>
... lines 48 - 52
</body>
</html>

Esto funciona.... simplemente por cómo está construida nuestra aplicación: funciona porque en getRoles(), nos aseguramos de que todo usuario conectado tiene al menos este rol:

... lines 1 - 12
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
... lines 15 - 81
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
... lines 90 - 154
}

Comprobando si Logged In: IS_AUTHENTICATED_FULLY

Genial. Pero esto me hace preguntarme: ¿hay una forma más "oficial" en Symfony de comprobar si un usuario está conectado? ¡Resulta que sí la hay! Comprobación deis_granted('IS_AUTHENTICATED_FULLY'):

<!DOCTYPE html>
<html>
... lines 3 - 14
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-1">
<div class="container-fluid">
... lines 18 - 26
<div class="collapse navbar-collapse" id="navbar-collapsable">
... lines 28 - 38
{% if is_granted('IS_AUTHENTICATED_FULLY') %}
... lines 40 - 43
{% endif %}
</div>
</div>
</nav>
... lines 48 - 52
</body>
</html>

Por cierto, todo lo que pasemos a is_granted() en Twig -como ROLE_USER oIS_AUTHENTICATED_FULLY - también podemos pasarlo al método isGranted() en el controlador, o a denyAccessUnlessGranted()... o a access_control. Todos ellos llaman al sistema de seguridad de la misma manera.

Seguro que te has dado cuenta de que IS_AUTHENTICATED_FULLY no empieza por ROLE_. ¡Sí! Los roles deben empezar por ROLE_... pero esta cadena no es un rol: la gestiona un sistema totalmente diferente: una parte del sistema de seguridad que simplemente devuelvetrue o false en función de si el usuario está conectado o no.

Así que, en la práctica, esto debería tener el mismo efecto que ROLE_USER. Cuando actualizamos... ¡sí! No hay cambios.

Registro de decisiones de acceso en el Perfilador

Haz clic en el enlace de seguridad de la barra de herramientas de depuración de la web para entrar en el perfilador. Desplázate hasta el final para encontrar algo llamado "Registro de decisiones de acceso". Esto es superguay: Symfony hace un seguimiento de todas las veces que se llamó al sistema de autorización durante la petición y cuál fue el resultado.

Por ejemplo, esta primera comprobación fue para ROLE_ADMIN, que probablemente viene deaccess_control: como fuimos a /admin, esta regla coincidió y se comprobó para ROLE_ADMIN. La siguiente comprobación es de nuevo para ROLE_ADMIN -probablemente para mostrar el enlace del administrador en Twig- y luego está la comprobación para IS_AUTHENTICATED_FULLYpara mostrar el enlace de entrada o salida. El acceso fue concedido para los tres.

Recordarme autentificado: IS_AUTHENTICATED_REMEMBER

Además de IS_AUTHENTICATED_FULLY, hay otro par de cadenas especiales que puedes pasar al sistema de seguridad. La primera es IS_AUTHENTICATED_REMEMBERED, que es súper potente... pero puede ser un poco confusa.

Así es como funciona. Si estoy conectado, entonces siempre tengoIS_AUTHENTICATED_REMEMBERED. Eso... hasta ahora debería sonar idéntico aIS_AUTHENTICATED_FULLY. Pero, hay una diferencia clave. Supongamos que me conecto, cierro el navegador, lo abro y lo actualizo... de modo que estoy conectado gracias a una cookie que me recuerda. En esta situación, tendré IS_AUTHENTICATED_REMEMBERED pero no tendré IS_AUTHENTICATED_FULLY. Sí, sólo tienes IS_AUTHENTICATED_FULLYsi te has conectado durante esta sesión del navegador.

Podemos ver esto. Dirígete a tu navegador, abre tus herramientas de depuración, ve a Aplicación y luego a Cookies. Oh... ¡mi cookie remember me ha desaparecido! Esto... fue un error que cometí. Cierra la sesión... y luego ve a security.yaml.

Anteriormente, pasamos de utilizar nuestro LoginFormAuthenticator personalizado a form_login. Ese sistema funciona totalmente con las cookies remember me. Pero también hemos eliminado la casilla de verificación de nuestro formulario de inicio de sesión. Y, dentro de nuestro autentificador, confiábamos en llamar a enable() en el RemmeberMeBadge para forzar la fijación de la cookie:

... lines 1 - 26
class LoginFormAuthenticator extends AbstractLoginFormAuthenticator
{
... lines 29 - 39
public function authenticate(Request $request): PassportInterface
{
... lines 42 - 44
return new Passport(
... lines 46 - 56
[
... lines 58 - 61
(new RememberMeBadge())->enable(),
]
);
}
... lines 66 - 81
}

El autentificador principal form_login añade definitivamente el RememberMeBadge, que anuncia que opta por el sistema "recuérdame". Pero no llama aenable() en él. Esto significa que tenemos que añadir una casilla de verificación al formulario... o, en security.yaml, añadir always_remember_me: true:

security:
... lines 2 - 16
firewalls:
... lines 18 - 20
main:
... lines 22 - 37
remember_me:
... lines 39 - 40
always_remember_me: true
... lines 42 - 54

Volvamos a conectarnos ahora: abraca_admin@example.com, contraseña tada y... ¡ya está! Ahí está mi cookie REMEMBERME.

Vale: como acabamos de iniciar la sesión -por lo que nos hemos "conectado durante esta sesión", estamos "autentificados completamente". Pero, si cerrara el navegador -lo que imitaré borrando la cookie de sesión- y actualizara... seguimos conectados, pero ahora estamos conectados gracias a la cookie remember me. Puedes verlo a través de RememberMeToken.

¡Y mira aquí arriba! ¡Tenemos los enlaces "Iniciar sesión" y "Registrarse"! Sí, ahora no estamos enIS_AUTHENTICATED_FULLY porque no nos hemos autentificado durante esta sesión.

Esto es una forma larga de decir que si utilizas las cookies "recuérdame", la mayoría de las veces debes utilizar IS_AUTHENTICATED_REMEMBERED cuando simplemente quieras saber si el usuario está conectado o no:

<!DOCTYPE html>
<html>
... lines 3 - 14
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light px-1">
<div class="container-fluid">
... lines 18 - 26
<div class="collapse navbar-collapse" id="navbar-collapsable">
... lines 28 - 38
{% if is_granted('IS_AUTHENTICATED_REMEMBERED') %}
... lines 40 - 43
{% endif %}
</div>
</div>
</nav>
... lines 48 - 52
</body>
</html>

Y luego, si hay un par de partes de tu sitio que son más sensibles -como quizás la página de "cambio de contraseña"- entonces protégelas con IS_AUTHENTICATED_FULLY. Si el usuario intenta acceder a esta página y sólo tiene IS_AUTHENTICATED_REMEMBERED, Symfony ejecutará realmente su punto de entrada. En otras palabras, los redirigirá al formulario de acceso.

Refresca la página y... ¡sí! Los enlaces correctos han vuelto.

PUBLIC_ACCESS y access_control

Vale, hay otras cadenas especiales similares a IS_AUTHENTICATED_REMEMBERED, pero sólo una más que creo que es útil. Se llama PUBLIC_ACCESS... y devuelve verdadero el 100% de las veces. Sí, todo el mundo tiene PUBLIC_ACCESS, aunque no esté conectado.

Así que... puede que pienses: ¿cómo es posible que eso sea útil? ¡Es una buena pregunta!

Mira de nuevo access_control en security.yaml. Para acceder a cualquier URL que empiece por /admin, necesitas ROLE_ADMIN:

security:
... lines 2 - 50
access_control:
- { path: ^/admin, roles: ROLE_ADMIN }
... lines 53 - 54

Pero imagina que tuviéramos una página de acceso en la URL /admin/login.

Vamos a crear un controlador ficticio para esto. En la parte inferior deAdminController, añade public function adminLogin()... con una ruta -/admin/login - y, dentro, devuelve un nuevo Response() con:

Finge que la página de inicio de sesión del administrador debe ser pública

... lines 1 - 10
class AdminController extends AbstractController
{
... lines 13 - 57
/**
* @Route("/admin/login")
*/
public function adminLogin()
{
return new Response('Pretend admin login page, that should be public');
}
}

Cierra la sesión... y ve a /admin/login. ¡Acceso denegado! Somos redirigidos a/login. Y realmente, si /admin/login fuera nuestra página de inicio de sesión, entonces seríamos redirigidos a /admin/login... que nos redirigiría a /admin/login... que nos redirigiría a /admin/login... que... bueno, ya te haces una idea: nos quedaríamos atrapados en un bucle de redirecciones. Y, además, ¡qué mal!

En security.yaml, queremos poder exigir ROLE_ADMIN para todas las URL que empiecen por /admin... excepto para /admin/login. La clave para hacerlo es PUBLIC_ACCESS

Copiar el control de acceso y pegarlo arriba. Recuerda: sólo coincide un access_control por petición y coincide de arriba a abajo. Así que podemos añadir una nueva regla que coincida con cualquier cosa que empiece por /admin/login y que requiera PUBLIC_ACCESS... ¡que siempre devolverá true!

security:
... lines 2 - 50
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
... lines 54 - 55

Gracias a esto, si vamos a cualquier cosa que empiece por /admin/login, sólo coincidirá con este access_control... ¡y se concederá el acceso!

Pruébalo: ve a /admin/login y... ¡se carga!

A continuación: hemos hablado de los roles y hemos hablado de denegar el acceso de varias formas diferentes. Así que pasemos al objeto User: cómo podemos preguntar a Symfony quién está conectado.

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
    }
}