Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Cerrar sesión y pasar datos de la API a JavaScript

Video not working?

It looks like your browser may not support the H264 codec. If you're using Linux, try a different browser or try installing the gstreamer0.10-ffmpeg gstreamer0.10-plugins-good packages.

Thanks! This saves us from needing to use Flash or encode videos in multiple formats. And that let's us get back to making more videos :). But as always, please feel free to message us.

¿Qué significa "cerrar sesión" en algo? ¿Como desconectarse de una API? Bueno, son dos cosas. En primer lugar, significa invalidar el token que tengas, si es posible. Por ejemplo, si tienes un token de API, le dirías a la API:

Haz que este token de API deje de ser válido.

En el caso de la autenticación de sesión, es básicamente lo mismo: significa eliminar la sesión del almacén de sesiones.

La segunda parte de "desconectarse" es hacer que quien esté utilizando el token lo "olvide". Si tuvieras un token de API en JavaScript, lo eliminarías de JavaScript. En el caso de la autenticación de sesión, significa eliminar la cookie.

Añadir la posibilidad de cerrar sesión

En cualquier caso, vamos a añadir la posibilidad de cerrar la sesión de nuestra autenticación basada en sesión. Volviendo a SecurityController, como antes, necesitamos una ruta y un controlador, aunque este controlador nunca será llamado. Llamaré al método logout() y devolveremos void. Verás por qué en un segundo. Dale a esto un Route de/logout y name: app_logout:

... lines 1 - 10
class SecurityController extends AbstractController
{
... lines 13 - 26
#[Route('/logout', name: 'app_logout')]
public function logout(): void
{
... line 30
}
}

La razón por la que he elegido void es porque vamos a lanzar una excepción desde dentro del método. Hemos creado esto completamente porque necesitamos una ruta: el sistema de seguridad de Symfony interceptará las cosas antes de que se llame al controlador:

... lines 1 - 10
class SecurityController extends AbstractController
{
... lines 13 - 26
#[Route('/logout', name: 'app_logout')]
public function logout(): void
{
throw new \Exception('This should never be reached!');
}
}

Para activar esa magia, en security.yaml, añade una clave llamada logout con pathdebajo configurada con ese nuevo nombre de ruta: app_logout:

security:
... lines 2 - 11
firewalls:
... lines 13 - 15
main:
... lines 17 - 22
logout:
path: app_logout
... lines 25 - 50

Esto activa un oyente que ahora está atento a las peticiones a /logout. Cuando haya una petición a /logout, cerrará la sesión del usuario y lo redirigirá.

Muy bien, aquí, nuestra aplicación Vue cree que no estamos conectados, pero lo estamos: podemos verlo en la barra de herramientas de depuración web. Y si vamos manualmente a /logout... ¡boom! Ya hemos cerrado la sesión de verdad.

Obtener los datos del usuario actual en JavaScript

Hace un momento hemos visto que, aunque hayamos iniciado sesión y la actualicemos, nuestra aplicación Vue no tiene ni idea de que hemos iniciado sesión. ¿Cómo podríamos solucionarlo? Una idea sería crear una ruta API /me. Entonces, al cargarse, nuestra aplicación Vue podría hacer una petición AJAX a esa ruta... que devolvería null o la información del usuario actual. Pero las rutas /me no son RESTful. Y hay una forma mejor: volcar la información del usuario en JavaScript al cargar la página.

Establecer una variable JavaScript de usuario global

Hay dos formas diferentes de hacerlo. La primera es estableciendo una variable global. Por ejemplo, en templates/base.html.twig, en realidad no importa dónde, pero dentro del cuerpo, añade una etiqueta script. Y aquí digamos window.user = y luego{{ app.user|serialize }}. Serializa en jsonld y añade una |raw para que no escape la salida: queremos JSON en bruto:

<!DOCTYPE html>
<html>
... lines 3 - 15
<body>
<script>
window.user = {{ app.user|serialize('jsonld')|raw }};
</script>
{% block body %}{% endblock %}
</body>
</html>

¿No es genial? En un minuto, lo leeremos desde nuestro JavaScript. Si refrescamos ahora mismo y miramos el código fuente, ¡sí! Vemos window.user = null. Y luego, cuando iniciemos sesión y actualicemos la página, fíjate: ¡ window.user = y una enorme cantidad de datos!

Serialización a JSON-LD en Twig

Pero ocurre algo misterioso: ¡tiene los campos correctos! Fíjate bien, tiene email, username y luego dragonTreasures, que es lo que son todas estas cosas. Además, correctamente, no tiene roles ni password.

¡Así que parece que está leyendo correctamente nuestros grupos de normalización! Pero, ¿cómo es eso posible? Sólo estamos diciendo "serializa este usuario a jsonld". Esto no tiene nada que ver con la API Platform y no está siendo procesado por ella. Pero... nuestros grupos de normalización están configurados en API Platform. Entonces, ¿cómo sabe el serializador que debe utilizarlos?

La respuesta, por lo que sé, es que funciona... en parte por accidente. Durante la serialización, API Platform ve que estamos serializando un "recurso API" y busca los metadatos de esta clase.

Eso está bien... pero en realidad no es perfecto... y de todas formas me gusta ser explícito. Pasa un 2º argumento a serializar, que es el contexto y establece groups enuser:read:

<!DOCTYPE html>
<html>
... lines 3 - 15
<body>
<script>
window.user = {{ app.user|serialize('jsonld', {
'groups': ['user:read']
})|raw }};
</script>
... lines 22 - 23
</body>
</html>

Ahora, observa lo que ocurre cuando actualizamos. Como antes, se expondrán las propiedades correctas enUser. Pero fíjate en la propiedad dragonTreasures incrustada. ¡Woh, ha cambiado! Antes estaba mal: incluía todo, no sólo lo que había dentro del grupo user:read.

Lectura de los datos dinámicos de Vue

Bien, vamos a utilizar esta variable global en JavaScript: enTreasureConnectApp.vue. Ahora mismo, los datos de user siempre empiezan como null. Podemos cambiarlo a window.user:

... lines 1 - 26
<script setup>
... lines 28 - 32
defineProps(['entrypoint']);
const user = ref(window.user);
... lines 35 - 39
</script>

Cuando actualicemos... ¡ya está!

Siguiente: si utilizas Stimulus, una forma aún mejor de pasar datos a JavaScript es utilizar valores Stimulus.

Leave a comment!

0
Login or Register to join the conversation
Cat in space

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

What PHP libraries does this tutorial use?

// composer.json
{
    "require": {
        "php": ">=8.1",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "api-platform/core": "^3.0", // v3.1.2
        "doctrine/annotations": "^2.0", // 2.0.1
        "doctrine/doctrine-bundle": "^2.8", // 2.8.3
        "doctrine/doctrine-migrations-bundle": "^3.2", // 3.2.2
        "doctrine/orm": "^2.14", // 2.14.1
        "nelmio/cors-bundle": "^2.2", // 2.2.0
        "nesbot/carbon": "^2.64", // 2.66.0
        "phpdocumentor/reflection-docblock": "^5.3", // 5.3.0
        "phpstan/phpdoc-parser": "^1.15", // 1.16.1
        "symfony/asset": "6.2.*", // v6.2.5
        "symfony/console": "6.2.*", // v6.2.5
        "symfony/dotenv": "6.2.*", // v6.2.5
        "symfony/expression-language": "6.2.*", // v6.2.5
        "symfony/flex": "^2", // v2.2.4
        "symfony/framework-bundle": "6.2.*", // v6.2.5
        "symfony/property-access": "6.2.*", // v6.2.5
        "symfony/property-info": "6.2.*", // v6.2.5
        "symfony/runtime": "6.2.*", // v6.2.5
        "symfony/security-bundle": "6.2.*", // v6.2.6
        "symfony/serializer": "6.2.*", // v6.2.5
        "symfony/twig-bundle": "6.2.*", // v6.2.5
        "symfony/ux-react": "^2.6", // v2.7.1
        "symfony/ux-vue": "^2.7", // v2.7.1
        "symfony/validator": "6.2.*", // v6.2.5
        "symfony/webpack-encore-bundle": "^1.16", // v1.16.1
        "symfony/yaml": "6.2.*" // v6.2.5
    },
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.4", // 3.4.2
        "mtdowling/jmespath.php": "^2.6", // 2.6.1
        "phpunit/phpunit": "^9.5", // 9.6.3
        "symfony/browser-kit": "6.2.*", // v6.2.5
        "symfony/css-selector": "6.2.*", // v6.2.5
        "symfony/debug-bundle": "6.2.*", // v6.2.5
        "symfony/maker-bundle": "^1.48", // v1.48.0
        "symfony/monolog-bundle": "^3.0", // v3.8.0
        "symfony/phpunit-bridge": "^6.2", // v6.2.5
        "symfony/stopwatch": "6.2.*", // v6.2.5
        "symfony/web-profiler-bundle": "6.2.*", // v6.2.5
        "zenstruck/browser": "^1.2", // v1.2.0
        "zenstruck/foundry": "^1.26" // v1.28.0
    }
}
userVoice