¿Te gustaría aprender .NET?
Tenemos los cursos que necesitas.¡Haz clic aquí!
Guardar datos en los JWT
Con un enfoque basado en cookies, simplemente guardamos el identificador de sesión.
Los tokens por otro lado nos permiten guardar cualquier tipo de metadata, siempre que se trate de un JSON válido.
La especificación de JWT indica que podemos incluir diferentes tipos de datos (llamados claims
), y que se pueden guardar como datos reservados, públicos y privados.
Dependiendo del contexto, podemos optar por usar una cantidad mínima de claims, y guardar sólo la identificación de usuario y el vencimiento del token, o bien podemos incluir claims adicionales, como el email del usuario, quién emitió el token, los alcances y/o permisos de los que dispone el usuario, etcétera.
Performance
Al utilizar una autenticación basada en cookies, desde backend se debe realizar una búsqueda de la sesión (correspondiente al identificador enviado por el cliente; ya sea en archivos, en una base de datos SQL tradicional o una alternativa NoSQL). En ese caso es muy probable que la ida y vuelta tome más tiempo si lo comparamos con la decodificación de un token. Además, como se pueden almacenar datos adicionales en los tokens (como el nivel de permisos), podemos disminuir la cantidad de búsquedas requeridas para obtener y procesar los datos solicitados.
Por ejemplo, supongamos que tenemos un recurso /api/orders
en nuestra API que devuelve las últimas órdenes registradas en nuestra aplicación, pero sólo los usuarios con rol administrador tienen acceso para ver esta data.
En un enfoque basado en cookies, una vez que se realiza la petición, desde backend es necesario hacer una consulta para verificar que la sesión es válida, otra búsqueda para acceder a los datos del usuario y verificar que tenga el rol de administrador, y finalmente una tercera consulta para obtener los datos.
Por otro lado, usando JWT, podemos guardar el rol del usuario en el token. Así, una vez que la petición se realiza y el token se valida, necesitamos realizar una sola consulta a la base de datos (para acceder a la información de las órdenes).
Listo para móviles
- Las APIs modernas no solo interactúan con el navegador.
- Escribir correctamente una API implica que pueda ser usada tanto por navegadores como desde plataformas móviles nativas (como iOS y Android).
- Las plataformas móviles nativas y las cookies no operan muy bien en conjunto, ya que se debe tener en cuenta toda una serie de consideraciones para su correcto funcionamiento.
- Los tokens, por otro lado, son mucho más fáciles de implementar (tanto en iOS como en Android). También son más fáciles de implementar para aplicaciones y servicios de Internet of Things (que no incorporan el concepto de gestión de cookies).
Preguntas comunes e inquietudes
En esta sección, veremos algunas preguntas y preocupaciones comunes que surgen con frecuencia cuando se trata el tema de autenticación basada en tokens.
El tema principal es la seguridad, pero examinaremos también el tamaño que pueden tener los tokens, el almacenamiento y la encriptación.
Tamaño de los JWT
La mayor desventaja de la autenticación basada tokens es el tamaño de los JWT.
Una cookie de sesión es relativamente pequeña en comparación (incluso) con el token más pequeño.
Dependiendo del caso, el tamaño de un token puede resultar problemático si lo cargamos con muchos claims.
Recuerda que cada solicitud al servidor debe incluir el correspondiente JWT.
¿Dónde almacenar los tokens?
Con una autenticación basada en tokens, tenemos la opción de escoger dónde guardar los JWT.
Comúnmente, los JWT son almacenados en el local storage
de los navegadores, y esto funciona bien para la mayoría de los casos.
Existen algunos inconvenientes a tener en cuenta si almacenamos los JWT en el local storage
(los mencionamos luego).
Podemos almacenar un token en una cookie, pero el tamaño máximo de una cookie es de 4kb, por lo que puede ser problemático si el token presenta varios claims. También podemos almacenar un token en el session storage
, que es similar al local storage
, pero se borra en el instante en que el usuario cierra el navegador.
Protección XSS y XSRF
Proteger a nuestros usuarios y servidores es siempre una prioridad.
Las preocupaciones más comunes que tienen los desarrolladores para decidir si usar o no la autenticación basada en tokens son acerca de la seguridad.
Dos de los vectores de ataque más comunes que enfrentan los sitios web son:
- Cross Site Scripting (XSS), y
- Cross Site Request Forgery (XSRF o CSRF).
Los ataques de Cross Site Scripting ocurren cuando una entidad externa puede ejecutar código sobre un sitio web o aplicación.
El vector de ataque más común aquí es si un sitio web presenta entradas (inputs) que no están debidamente validadas.
Si un atacante puede ejecutar código Javascript sobre tu dominio, tus JSON Web Tokens son vulnerables.
Muchos frameworks, automáticamente validan (desinfectan) las entradas de datos y evitan la ejecución de código arbitrario.
Si no estás utilizando un framework (que realice esta validación), puedes usar también plugins (como Caja Compiler
, una herramienta desarrollada por Google para ayudar con esta tarea).
Se recomienda usar un framework o plugin para tener este problema resuelto, versus la alternativa de crear una solución propia.
Los ataques de Cross Site Request Forgery no son un problema si estás utilizando JWT con el local storage
. Por otro lado, si almacenas el JWT en una cookie, deberás protegerte contra XSRF.
Afortunadamente, prevenir los ataques XSRF no es muy complicado. En resumen: para protegernos en contra de ataques XSRF, nuestro servidor, al establecer una sesión con un cliente debe generar un token único (es importante tener en claro que no es un JWT). Luego, cada vez que se envíen datos al servidor, un campo de entrada oculto (hidden input field
) contendrá este token y el servidor lo validará para asegurarse de que los tokens coincidan.
Otra buena forma de proteger a nuestros usuarios y servidores consiste tener un tiempo de expiración corto para los tokens. De esta forma, incluso si un token se ve comprometido, rápidamente se volverá inútil. Además, podemos mantener una lista negra (blacklist
) de tokens comprometidos y así evitar que estos tokens puedan usarse. Finalmente, un enfoque definitivo sería cambiar el algoritmo de firma, lo que invalidaría todos los tokens activos y requeriría que todos los usuarios inicien sesión de nuevo. Este enfoque no es recomendable, pero está disponible en caso de una infracción grave.
Los Tokens son firmados, mas no encriptados
Un JSON Web Token se compone de 3 partes: header, payload, y signature.
El formato de los JWT consiste en unir estas partes usando un punto entre ellas: header.payload.signature
.
Por ejemplo, si tuviéramos que firmar un JWT con el algoritmo HMACSHA256, la clave secreta ‘shhhh’ y el siguiente contenido (payload):
{
"sub": "1234567890",
"name": "Ado Kukic",
"admin": true
}
El JWT generado sería:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbyBLdWtpYyIsImFkbWluIjp0cnVlLCJpYXQiOjE0NjQyOTc4ODV9.Y47kJvnHzU9qeJIN48_bVna6O0EDFiMiQ9LpNVDFymM
Lo más importante a tener en cuenta aquí, es que este token está firmado usando el algoritmo HMACSHA256, pero el encabezado (header) y los datos (payload) están codificados en Base64URL (no están encriptados).
Si vamos a jwt.io, pegamos el token y seleccionamos el algoritmo HMACSHA256 (abreviado como HS256), podemos decodificar el token y leer su contenido. Por lo tanto, no hace falta decir que datos confidenciales, como contraseñas, nunca deben almacenarse en el payload.
Si necesitas almacenar datos confidenciales en el payload, puedes usar JSON Web Encryption (JWE). JWE permite cifrar el contenido de un JWT para que no sea legible por nadie más que por el servidor. Aunque es posible encriptar el contenido de un token, no es realmente necesario para un sistema de autenticación (lo que sí es importante es que usemos el protocolo HTTPS para que los mensajes que intercambiamos con el servidor viajen cifrados).
Autenticación: Uso de JWT vs sesiones
Los JWT proveen un mecanismo para mantener el estado de una sesión en el lado del cliente, en vez de hacerlo en el servidor.
Por lo tanto, una pregunta más adecuada sería, «¿Cuáles son los beneficios de usar JWT sobre usar sesiones del lado del servidor?» (server-side sessions).
Con las server-side sessions es necesario guardar las sesiones activas en una base de datos o en memoria; y por otro lado asegurar que cada cliente siempre sea atendido por el mismo servidor. Ambos presentan inconvenientes.
- Si se usa una base de datos (u otro almacenamiento centralizado), esto puede convertirse en un cuello de botella (una preocupación más), ya que se requiere realizar una consulta al atender cada request (petición).
- Con una solución en memoria limitamos nuestro escalamiento horizontal, y las sesiones son afectadas por problemas de red (como el reinicio de servidores).
Mover la sesión al lado del cliente significa que ya no necesitamos mantener una sesión del lado del servidor, pero nos encontramos con nuevos desafíos:
- Guardar los tokens de forma segura
- Transportarlos de forma segura
- Los JWT (que representan a las sesiones) pueden ser difíciles de invalidar
- Asegurar confiabilidad sobre los datos enviados por el cliente
Estos problemas los comparten todos los mecanismos de sesión del lado del cliente (como los JWT).
JWT de forma particular ya soluciona el último de los puntos mencionados.
¿Pero qué es un JSON Web Token?:
Es una cadena, un conjunto de caracteres, que contiene un poco de información. Para las sesiones de usuario puede incluir el username y el tiempo de expiración (fecha y hora). Pero en realidad puede representar lo que sea, incluso un identificador de sesión o el perfil completo del usuario que ha iniciado sesión. Aunque, por favor, esto debe evitarse.
Tiene una firma segura que evita que agentes externos malintencionados generen tokens falsos. Se necesita acceder a la clave privada del servidor para firmarlos; y gracias a esta firma se puede verificar que no se hayan modificado (desde que el servidor los firmó).
Se envían en cada petición (tal como sucede con las cookies). Comúnmente se envían a través del Authorization header
de las peticiones HTTP, pero curiosamente también se pueden usar cookies para transportarlos.
Cada token es firmado, y así el servidor puede verificar su validez. El servidor confía en su habilidad para firmar los tokens de forma segura. Para esto existen bibliotecas estándares, que se recomiendan sobre las implementaciones que uno mismo pueda realizar.
A fin de transportar de manera segura el token, lo adecuado es enviarlo a través de un canal encriptado (generalmente httpS
).
Con respecto al almacenamiento seguro del token en el cliente, debemos asegurar que los delincuentes no puedan acceder a ellos. Esto (principalmente) significa evitar que se cargue JS de sitios ajenos sobre nuestra página, porque con ello es posible leer el token y por tanto capturarlo. Esto se mitiga utilizando las mismas estrategias utilizadas para mitigar ataques XSS.
Si tienes la necesidad de invalidar los JWT, definitivamente hay formas de lograrlo.
Almacenar datos en una tabla temporal solo para usuarios que han solicitado que «se cierren sus otras sesiones» es una muy buena alternativa.
Si una aplicación necesita invalidar sesiones de forma específica, de la misma manera se puede guardar el ID de cada sesión y tener una tabla de «tokens desactivados». Esta tabla solo necesita conservar registros en base a la máxima duración permitida para los tokens.
Como habrás notado, la capacidad de invalidar tokens niega parcialmente el beneficio de las sesiones del lado del cliente, en el sentido de que se debe mantener un estado en el servidor (de las sesiones desactivadas). Pero, esta tabla ha de ser mucho más pequeña que la tabla de sesiones original (server-side sessions), por lo que las búsquedas todavía siguen siendo más eficientes.
Otra ventaja del uso de JWT es que resulta fácil de implementar utilizando las bibliotecas disponibles en (probablemente) todos los lenguajes que puedan existir. También está completamente divorciado de su esquema de autenticación: si se pasa a usar un sistema basado en huellas dactilares, no es necesario realizar ningún cambio en el esquema de administración de la sesión.
En resumen, JWT resuelve algunas de las deficiencias de otras técnicas de sesión:
- Autenticación «más barata», porque puede eliminar un consulta a la base de datos (¡o al menos tener una tabla mucho más pequeña para consultar!), lo que a su vez habilita la escalabilidad horizontal.
- Datos del lado del cliente «a prueba de manipulaciones».
Si bien los JWT no responden a otros problemas, como el almacenamiento seguro o el transporte, no suponen en realidad ningún nuevo problema de seguridad.
Existe mucha crítica negativa sobre los JWT, pero si se implementan con el mismo cuidado que otros tipos de autenticación, al final es lo mismo.
Una nota final: no es correcto comparar Cookies vs Tokens. Las cookies son un mecanismo para almacenar y transportar datos, y por tanto también se pueden usar para almacenar y transportar JSON Web Tokens.
Te esperamos en la segunda parte del artículo en donde hablaremos mas acerca de estos temas, los cuales hoy en día son de vital importancia en el mundo de la tecnología.
¿Te gustaría aprender .NET?
Tenemos los cursos que necesitas.¡Haz clic aquí!