JWT & Refresh Tokens 🔒
Tags: jwt, typescript, node, backend
Published: 2024-03-24 00:35:12

JWT o Json Web Token es un estándar para la autenticación y transferencia segura de datos entre dos partes. Usa la encriptación en base64 y una firma por medio de una clave privada o publico/privada para poder verificar la validez del token.

Estos tokens cuentan con una estructura en particular
 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/74kf9hr5pymmjlls5oma.png)

El header o encabezado contiene la información del tipo de token utilizado y el tipo de algoritmo usado para la encriptación. Por otro lado, el payload o carga útil hace referencia a los datos suministrados por el desarrollador y cargados dentro del token en formato JSON. Por ultimo tenemos el signature o la firma que consiste en una clave secreta para ser usada como método de verificación de la validez del token. 

> Para mayor información referirse a: https://jwt.io/

Ahora bien, en el contexto de una aplicación que requiera un proceso de autenticación y autorización de usuarios por lo general hacemos uso de JWT para generar tokens los cuales cargamos, dentro del payload del token, con información que nos permita verificar en nuestra base de datos que esa persona sea quien dice ser o cuente con los roles y permisos que se requieren para acceder a un recurso en especifico.

> Es importante resaltar que el payload utilizado no debe contener información susceptible del usuario como contraseñas, cuentas bancarias entre otras. 

Para lograr esto por lo general tenemos un flujo similar a este 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0yoqto0fl3dxsxmj58yy.png)

En la anterior imagen observamos como un cliente inicia sesión dentro de nuestra aplicación, enviando mediante una solicitud HTTP, un email y una contraseña la cual verificamos en nuestra base de datos y si dichos datos coinciden devolvemos un `ACCESS_TOKEN` el cual el cliente almacenara, ya sea en el local storage o en el session storage, y deberá enviar en cada solicitud que realice dentro de nuestra aplicación para autorizar el consumo de los recursos que requiera. 

> La forma en como enviar el `ACCESS_TOKEN` desde el servidor dependerá de tu aplicación. Este token puede ser enviado mediante cookies o mediante una respuesta HTTP ordinaria. Cada una tiene ventajas y desventajas que deben ser consideradas.

Este `ACCESS_TOKEN` contara con un tiempo de expiración el cual configuraremos de acuerdo a las necesidades tanto de seguridad como de experiencia de usuario que nuestra aplicación requiera. 

Debemos considerar que entre mas largo sea el tiempo de expiración de nuestros tokens mas vulnerable será nuestra aplicación en términos de seguridad. Por otro lado, entre mas corto sea dicho tiempo, nuestros usuarios deberán hacer login nuevamente para obtener un `ACCESS_TOKEN` nuevo lo cual entra en detrimento de la experiencia de usuario.

### REFRESH TOKENS

Hasta este punto no se ha dicho nada diferente a lo que es normalmente un sistema de autenticación y autorización común que implemente JWT. 

Los refresh tokens nos permiten lograr que nuestro usuario no tenga que realizar un login nuevamente cada que el ACCESS_TOKEN expira y lo logra mediante el envío de un token extra o REFRESH_TOKEN cuando el usuario hace login. 

La diferencia a este punto es que ambos tokens tienen un tiempo de expiración diferente. Mientras el ACCESS_TOKEN puede llegar a tener 20s el REFRESH_TOKEN puede llegar a tener 6 meses o 1 año de expiración y al vencerse el primer token el usuario utilizara el `REFRESH_TOKEN` como token de acceso para generar un nuevo `ACCESS_TOKEN` y de esta forma mantener activa la sesión dentro de nuestra aplicación. 

Es posible llegar a pensar que esta solución no es realmente útil ya que simplemente aumentando el tiempo de expiración del `ACCESS_TOKEN` nos ahorraría tener que enviar un segundo token o podríamos pensar ¿Qué sucede si el `REFRESH_TOKEN` es hurtado? ¿No pondríamos en riesgo la seguridad de nuestro sistema al tener un tiempo de expiración tan largo?

### ¿Por qué enviar dos tokens de acceso?

Es importante resaltar que el `REFRESH_TOKEN` nos permite manejar un concepto llamado SESIONES dentro de nuestra aplicación la cual por lo general es una tabla en nuestra base de datos. 

La idea de generar un segundo token es poder mediante este token generar un `ACCESS_TOKEN` nuevo para el usuario en caso de que dicho `ACCESS_TOKEN` haya sido expirado y solo generaremos un nuevo `ACCESS_TOKEN` si el usuario nos envía el `ACCESS_TOKEN` expirado y el `REFRESH_TOKEN` existe dentro de nuestro registro de sesiones y es valido como JWT. 

Dicho lo anterior, nuestro proceso de autorización ahora no solo consultara la validez del `REFRESH_TOKEN` y del `ACCESS_TOKEN` o si el usuario que esta accediendo a los recursos de nuestra aplicación existe, sino que ahora consultara una tabla llamada SESIONES que almacenara los `REFRESH_TOKENS` activos y, si dicho token existe y esta activo, generara un nuevo `ACCESS_TOKEN` para acceder a los recursos de nuestra aplicación. 

De esta forma estaremos generando `ACCESS_TOKENS` continuamente solo y solo si nuestro usuario cuente con un `ACCESS_TOKEN` valido y un `REFRESH_TOKEN` valido. 

> Debemos resaltar que no es lo mismo validez del token que tiempo de expiración del token. Nuestro token pudo haber expirado pero sigue siendo valido. Los JWT no pueden ser invalidados y solo podrán ser manejados mediante un tiempo de expiración lo cual no necesariamente quiere decir invalidez

### ¿Qué sucede si el `REFRESH_TOKEN` es hurtado? ¿No pondríamos en riesgo la seguridad de nuestro sistema al tener un tiempo de expiración tan largo?

Recordemos que la forma de acceder a nuestra aplicación es mediante el `ACCESS_TOKEN` y si este ha expirado generaremos otro nuevo mediante la validación del `REFRESH_TOKEN`. Por lo tanto solo podemos generar un nuevo `ACCESS_TOKEN` si contamos tanto con el `ACCESS_TOKEN` y el `REFRESH_TOKEN` valido. 

Si un atacante logra hurtar nuestro `ACCESS_TOKEN` solo tardara 5s, o menos si así lo configuramos, para ser expulsado de la aplicación ya que no podrá generar uno nuevo al no tener el `REFRESH_TOKEN`.

Por otro lado, si un atacante logra hurtar nuestro REFRESH_TOKEN no podrá generar un `ACCESS_TOKEN` ya que, para generar uno, necesita el primer `ACCESS_TOKEN` generado al momento de hacer login. 

Podemos observar aquí la importancia de tener un buen manejo de seguridad en nuestros tokens como por ejemplo:

- Configurar tiempos cortos en el ACCESS_TOKEN.
- Transferir nuestros tokens mediante las cabeceras HTTP.
- Si usamos cookies asegurarnos de usar la bandera HttpOnly y secure para evitar ataques XSS.
- Limitar el tiempo de expiración de nuestras cookies.

Llegados a este punto encontramos un flujo similar a este

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iaryumy4e5ei0w1j01mh.png)

Ahora veremos de que forma, desde el código, podemos actualizar nuestro `ACCESS_TOKEN` haciendo uso de nuestro `REFRESH_TOKEN`.

### IMPLEMENTACIÓN 

En este ejemplo utilizaremos Node y Express para ejemplificar este proceso y haremos toda nuestra lógica dentro del middleware de autorización. Debemos recordar que nuestro proceso de autenticación, donde estamos generando nuestros dos tokens, ya esta configurado y nos centraremos en como actualizar nuestro `ACCESS_TOKEN` utilizando nuestro `REFRESH_TOKEN`. 

Sin embargo, debemos realizar un pequeño ajuste a nuestro proceso de autenticación ya que es aquí donde haremos el registro de nuestro `REFRESH_TOKEN` en la tabla que anteriormente llamamos `SESIONES`. 

El código luce algo así: 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3imndpnfw3ze1d0epp7m.png)

1. Podemos observar como nuestro servicio de autenticación, que es llamado desde nuestro controlador de login, primero valida la existencia del usuario a través del método `getUserByEmail`.

2. Utilizando la librería bcrypt validamos la contraseña y, si este proceso arroja un error, devolvemos a través del manejador de errores el código correspondiente.

3. Si la validación de la contraseña fue bien registramos en nuestra tabla `SESIONES`, usando el método `createSession`, la sesión de nuestro usuario.  

4. Como ultimo paso, firmamos ambos tokens y configuramos el tiempo de expiración para cada uno. Recordemos que el `ACCESS_TOKEN` por lo general cuenta con un tiempo muy corto de expiración y el `REFRESH_TOKEN`, por el contrario, se configura con un tiempo largo de expiración. 

De esta forma tenemos nuestro proceso de autenticación ahora configurado para que nos devuelva ambos tokens y nos registre una sesión por cada usuario con un login activo. 
 

### Proceso de autorización

Por lo general tendemos a realizar el proceso de autorización, para cada uno de los recursos de nuestra aplicación, mediante un middleware. Es en este en donde en este ejemplo realizaremos todo el proceso de validación y generación `ACCESS_TOKEN`. 

Una vez nuestro usuario haya accedido a nuestra aplicación y este haya guardado el `ACCESS_TOKEN` y el `REFRESH_TOKEN` utilizaremos nuestro middleware de autorización para validar si nuestro usuario cuenta con los permisos suficientes para consultar el recurso que esta solicitando. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tziktek7jg0vwd2qil91.png)

En la anterior imagen observamos todo el proceso de autorización completo pero vamos a revisarlo linea por linea

1. En este ejemplo estamos enviando los tokens por medio de el header `Authorization` y un header personalizado que llamamos `x-refresh-token` por lo tanto lo primero que realizamos es la validación acerca de la existencia o no de ambos tokens. Si estos no existen, enviaremos un error de tipo `Forbbiden`. 

2. En las siguientes lineas tenemos una segunda capa de validación en donde, usando la librería JOI, validamos si los datos dentro de estas cabeceras son de tipo `string`.

3. dentro del `try...catch` verificamos el `ACCESS_TOKEN` y accedemos a los valores `payload` y `expired`. Si el payload existe, creamos un valor nuevo dentro del objeto `req` que llamamos `user` y lo cargamos con lo que viene en el payload. Por último, ejecutamos la función `next` la cual nos permite avanzar al controlador. 

    > Observemos que existe un @ts-ignore ya que para crear valores dentro del objeto `Request` de Express debemos realizar una configuración extra la cual no entra en el contexto de este blog. Si deseas saber más al respecto puedes leer como hacerlo [aquí] 
    (https://dev.to/jeanvittory/expandiendo-la-interfaz- request-de-express-con-dts-opn)

4. Si ocurrió algún problema con la verificación de nuestro `ACCESS_TOKEN` nuestro método verifyJWT nos devolverá un objeto con el payload `null` y el expired `jwt expired`.

    ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lfosxvi0iyen78mfbpbx.png)

5. Luego de esto validamos de que si nuestra propiedad `expired` tiene un valor `truthy` y nuestro `REFRESH_TOKEN` fue validado con éxito validaremos nuestro `REFRESH_TOKEN` usando el método `verifyWT`. Caso contrario devolveremos un objeto con una propiedad payload en `null`.

6. Si nuestro `REFRESH_TOKEN` no fue validado con éxito devolveremos un error de `Unauthorized`.

7. Caso contrario, avanzaremos con nuestro flujo y consultaremos en nuestra base de datos el `sessionId` que venia en el payload de nuestro `REFRESH_TOKEN` y si esta consulta no encuentra ningún resultado volveremos a arrojar un error de tipo `Unauthorized`

8. Por el contrario, si nuestra consulta encuentra un sessionId activo firmaremos un nuevo `ACCESS_TOKEN`.

9. El payload de este nuevo token lo ingresaremos en la propiedad user de nuestro objeto `Request`

10. Por último, agregaremos este nuevo `ACCESS_TOKEN` dentro de la cabecera `Authorization` concatenando la palabra `Bearer` al inicio. Finalizaremos ejecutando la función next  para avanzar a nuestro controlador.    

De esta forma podemos crear un sistema de tokens el cual hace uso de los `REFRESH_TOKEN`. Si bien este sistema no es infalible podemos mejorar ciertos aspectos de nuestro sistema como lo hemos visto a través de esta lectura.  

Gracias por leer 😊