Binary Coffee

Conociendo JWT desde dentro

algorithms jwt
## Como funcionan las sessiones en la web? Alguna vez se preguntaron como funcionaban las sessiones al autenticarse en un sitio web. Pues la respuesta es simple, usando **Cookies**. Una **Cookie** no es más que información de estado que se provee por el servidor en cada petición que el cliente hace. Como muestra la siguiente imagen, el cliente hace la petición y el servidor retorna dicha petición con la información deseada por el cliente, pero además agrega un bloque de información que el cliente retornara al servidor en cada una de las venideras peticiones. ![](https://api.binary-coffee.dev//uploads/065fb773f1114c48a5c489221774f947.jpg) En cada petición el cliente envía al servidor las **Cookies** que tenía anteriormente y las actualiza en cada respuesta. Todo este proceso ocurre a nivel de *navegador web* y no es necesaria ninguna implementación por parte de los desarrolladores. Pero que ocurre cuando no es un navegador quien hace las peticiones al servidor. Actualmente contamos con aplicaciones de escritorio, aplicaciones móviles, aplicaciones de una sola página (SPA como Angular, React o Vue), he incluso tenemos aplicaciones backend que utilizan las APIs públicas de otros backend. En cada uno de estos ejemplos el uso de **Cookies** no es tan trivial, puesto que sería necesaria la implementación a medida de un sistema de **Cookies**. ## Qué es JWT (JSON Web Token) En este contexto surge **JWT** (Token Web en formato JSON), que no es más que una manera de dar credenciales a los clientes pero esta vez, sin navegador de por medio, sino el mismo cliente quien los maneja. **JWT** parte exactamente de la misma idea anterior, donde: 1. el usuario se autentica 2. el servidor le provee una llave o identificación 3. el cliente utiliza este credencial en cada una de las venideras peticiones ## Estructura de JWT La estructura de **JWT** es bien sencilla de entender, y cuenta de 3 partes principales: 1. Header: se definen características de token, tales como algoritmo utilizado para firmar el token y el tipo de token 2. Payload: en esta área se definen algunas caracteristicas generales del token y al mismo tiempo se pueden inyectar datos que el servidor estime necesarias: como id del usuario en la base de datos, tiempo de expiración del token y datos semejantes. 3. Signature: esta área es en efecto un hash de las dos áreas anteriores y es lo que agrega la seguridad al token *Ejemplo de JWT* ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Imd1aWxsZSIsImV4cCI6MTU3OTgxNTE3MzEwM30.NGbqg1LsY5YrTJ4wCtZungp91Mf-FzFJJW70INs5ixY ``` Como dentro de un JSON es posible almacenar cualquier información incluso caracteres que no es posible enviar en una petición, por este motivo es que se convierten a base64 cada una de las secciones del token. Si miramos más de cerca el JWT anterior, se separa en tres partes \<head>.\<payload>.\<signature>. A continuación se muestra el JWT deserializado: ``` { "alg": "HS256", "typ": "JWT" } { "id": "guille", "exp": 1579815173103 } NGbqg1LsY5YrTJ4wCtZungp91Mf-FzFJJW70INs5ixY ``` Gracias al signature, ningún usuario puede modificar atributos dentro del JWT, porque esto a su vez generaría un signature completamente distinto y no sería un token válido. De esta misma manera, si el servidor inyecta en el token un parámetro como el del ejemplo anterior *exp*, que es un tiempo de validez, es otra forma en la que se garantiza la seguridad del token, ya que como puede imaginarse, JWT está diseñado para no almacenar información en el lado del servidor y de esta forma agilizar la velocidad con que se atienden las peticiones de los usuarios. ## Implementando nuestro propio JWT con la librería crypto de NodeJS El primer paso para la implementación será crear la función de hash. Un hash, para quien no sepa lo que es: no es más que una función matemática que dada una entrada retorna una salida única. Se tienen diferentes clasificaciones de hash, pero este tema será tratado más adelante en próximos artículos, de momento se desarrollará el artículo con la definición antes mencionada. La librería **crypto** de **NodeJS**, trae una serie de implementaciones de hash, pero la que se utilizará en el ejemplo será **HMAC**. A continuación se muestra la función a usarse para generar el signature: ``` const crypto = require('crypto'); function createHash(text, key) { return crypto.createHmac('sha256', key).update(text).digest('base64'); } ``` Ahora que se cuenta con la función para generar el hash, que tecnicamente se le pasa un texto y una llave y la función retorna un hash o valor único, se implementará la función para crear el **JWT**: ``` function createJwt(payload, key) { const head = Buffer.from(JSON.stringify({alg: "HS256", typ: "JWT"})).toString('base64'); payload = Buffer.from(JSON.stringify({ ...(payload || {}), exp: new Date().getTime() + 180000 })).toString('base64'); const sign = createHash(head + '.' + payload, key); return head + '.' + payload + '.' + sign; } ``` Explicando los pasos en el código anterior: 1. *line 2* se convierte a string el header `{alg: "HS256", typ: "JWT"}` y seguidamente a base64 2. *line 3* se utiliza el payload de los parámetros y se le inyecta una fecha de expiración y seguidamente se convierte a string y se lleva a base64 3. *line 7* a partir de la concatenación del head y el payload se genera el hash que será el signature del token 4. *line 8* se retorna el **JWT**, que la unión en base64 del \<head>.\<payload>.\<signature> Ahora se mostrará la función para verificar la validez del **JWT**: ``` function checkJwt(jwt, key) { const splitJwt = jwt.split('.'); if (splitJwt.length === 3) { try { if (createHash(splitJwt[0] + '.' + splitJwt[1], key) === splitJwt[2]) { const payload = JSON.parse(Buffer.from(splitJwt[1], 'base64').toString()); return parseInt(payload.exp) > new Date().getTime(); } } catch (ignore) {} } return false; } ``` Explicando el código línea a línea: 1. *line 2* se divide en un arreglo los tres elementos del token (head, payload y signature) 2. *line 3* se comprueba que sea válido el token, partiendo del hecho que debe tener exactamente 3 secciones 3. *line 5* se genera el hash con la función de hash y utilizando el key que solo conocemos nosotros. Si el resultado es diferente al signature del **JWT** entonces no es un token válido 4. *line 6-7* se extrae el objeto payload y se comprueba el tiempo de expiración del token Ahora que se tiene todo lo necesario para generar y comprobar un **JWT**, se usarán las funciones antes vistas: ``` const JWT = createJwt({id: 'tmpId', name: 'guille'}, 'this-is-my-private-credential'); console.log(JWT); console.log(checkJwt(JWT, 'this-is-my-private-credential')); ``` Se generó un **JWT** en *line 1* y es comprobado en *line 4*, la salida del script seberá ser algo como esto: ``` eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6InRtcElkIiwibmFtZSI6Imd1aWxsZSIsImV4cCI6MTU4MDAzNzMyNjQ1NX0=.M3nR3/1AyZ5FgFFalB1U2daeQFSfl9oXJjR6w/pd+/M= true ``` El código completo del ejemplo visto se encuentra en github en este [link](https://gist.github.com/wil92/a484dbe5d0ddc0601f4f9587cc53f6e5) ## Referencias - [jwt.io](https://jwt.io/introduction/): Página oficial de JWT - [JWT Debugger](https://chrome.google.com/webstore/detail/jwt-debugger/ppmmlchacdbknfphdeafcbmklcghghmd): Extensión para Chrome - [nodejs.org](https://nodejs.org/en/knowledge/cryptography/how-to-use-crypto-module/): Artículo sobre el módulo crypto Happy coding!
Opiniones