Entendiendo la autenticación JWT

2020-06-02

post-thumb

Índice

En este artículo, veremos qué es el JSON Web Token.

Pasaremos por una explicación básica del JWT, su estructura y, finalmente, crearemos un servidor simple que tomará algunos datos y los insertará en un JWT.

¿Qué es un JSON Web Token y por qué lo necesitamos?

El JSON Web Token (JWT) es una manera segura, compacta e independiente de transmitir información entre varias partes en forma de un objeto JSON.

Imagina un login en una aplicación, como Tinder. Tinder permite que los usuarios inicien sesión usando su perfil de Facebook. Por lo tanto, cuando el usuario selecciona la opción de iniciar sesión usando Facebook, la aplicación contacta al servidor de autenticación de Facebook con las credenciales del usuario (nombre de usuario y contraseña).

Después de que el servidor de autenticación verifique las credenciales del usuario, creará un JWT y lo enviará al usuario. La aplicación ahora obtiene este JWT y permite al usuario acceder a sus datos.

La estructura del JWT

Un JSON Web Token consiste en tres partes separadas por un ".". Son:

  • Header (Encabezado)
  • Payload (Cuerpo del token)
  • Firma

El header generalmente consiste en dos partes: el tipo del token y el algoritmo de hash que se está utilizando.

{
   "alg": "HS256",
   "typ": "JWT"
}

Payload

El payload es donde se almacenan las informaciones reales que queremos enviar. Aquí hay un ejemplo de payload simple. Ten en cuenta que los payloads pueden ser mucho más complicados que esto para garantizar mejor seguridad.

{
  "id": 42,
  "name": "Laura Esteves",
  "admin": true
}

Signature

La firma (signature) se usa para verificar si el mensaje no fue alterado antes de llegar a su destino. Esto generalmente se hace usando claves privadas.


Estas tres partes generalmente se codifican en tres secuencias de Base64-URI que están separadas por un . entre ellas.

Para decodificar, verificar, generar o simplemente jugar con JWTs, consulta el JWT.IO Debugger .

Ahora que tenemos un entendimiento básico de lo que es un JWT, veamos cómo crear un servidor de autenticación simple que emita el JWT, que usaremos para acceder a una API.


Iniciando el proyecto

Escribí un proyecto simple de API que usaré aquí para que entendamos cómo funciona el JWT.

Basta clonar el proyecto en Github, y ejecutar npm install o yarn install dependiendo del package manager que estés usando.

Notarás que dentro del index.js hay todo el código para que el servidor funcione, con nuestras rutas. Puedes usar VSCode para abrir el proyecto.

Bibliotecas necesarias

import cors from 'cors'
import express from 'express'
import jwt from 'jsonwebtoken'
import expressjwt from 'express-jwt'
import bodyParser from 'body-parser'

express es la biblioteca que creará nuestro servidor web, express-jwt, body-parser y cors son como plugins para express, que ayudan con la generación del JWT, parsing de JSON y CORS. jsonwebtoken es una lib que facilita la verificación de la firma de un JWT.

También creé un archivo llamado users.js que contiene los usuarios y contraseñas ficticios de nuestro sistema. En lugar de usar una base de datos y encriptar la contraseña, esta es la forma más simple de mostrar las funcionalidades del JWT.

import users from './users.js'

Montando el servidor y el validador de JWT

En las próximas líneas, instancio el servidor que tendrá las rutas que vamos a usar y uso la biblioteca mostrada arriba para poder inyectar en express y validar la autenticación.

const app = express()
const SECRET = 'Sup3rS3cr3t'
const isAuthenticated = expressjwt({ secret: SECRET })

La constante SECRET se usa como nuestro secreto a la hora de encriptar y desencriptar nuestro token de autenticación. Normalmente se usa una clave privada RSA pero para mantener nuestro ejemplo simple y enfocado en JWT, usaremos solo una string.

app.use(cors())
app.use(bodyParser.json())
app.set('port', process.env.PORT || 3000)

Agrego el cors a nuestro servidor, que será útil para acceder a la API desde otro dominio donde el frontend pueda estar. El bodyParser se usa para parsear el body de las requests y el set port en la app se usa para colocar el puerto (sea vía una variable de ambiente PORT o con el valor predeterminado 3000).

Rutas de nuestra aplicación

GETs

// Routes
app.get('/', (req, res) => res.status(200).send(`Hola mundo`))

app.get('/public', (req, res) =>
  res.status(200).send('¡Todo el mundo puede ver esto!')
)

app.get('/secret', isAuthenticated, (req, res) =>
  res.status(200).send('Solo personas logueadas pueden verme')
)

Tenemos 2 rutas públicas / y /public que al ser llamadas, retornan los respectivos textos y la /secret que vamos a usar para entender la función del JWT, ya que no podemos llamarla si no estamos logueados.

La ruta /secret al ser llamada vía browser, producirá un error por no estar logueados.

Usando postman para autenticarse

El archivo Exemplo_de_JWT.postman_collection.json dentro del proyecto será usado para importar en postman y hacer las llamadas al servidor usando el JWT.

Ahora que creamos un servidor simple que puede manejar solicitudes GET y POST, vamos a crear un emisor de JWT simple. El archivo users.js previamente declarado contiene los usuarios junto con sus contraseñas, como se muestra a continuación:

export default [
  { id: 1, username: 'clarkKent', password: 'superman' },
  { id: 2, username: 'bruceWayne', password: 'batman' },
]

Ahora vamos a escribir la ruta /login. Primero verificaremos si la solicitud enviada contiene un nombre de usuario y una contraseña. Si no es el caso, el servidor deberá responder con un status 400. Después buscaremos en nuestros usuarios para intentar encontrar uno que corresponda con el username y password registrado. Si el usuario y contraseña no son correctos, devolveremos un 401, que significa no autorizado.

Si todo está bien y el usuario se ha autenticado, crearemos un token válido para él y lo devolveremos. Nota que el SECRET se usa para firmar el token.

app.post('/login', (req, res) => {
  const { username, password } = req.body || {}

  if (!username || !password) {
    return res
      .status(400)
      .send('Error. Ingrese el nombre de usuario y contraseña correctos')
  }

  const user = users.find((u) => {
    return u.username === username && u.password === password
  })

  if (!user) {
    return res.sendStatus(401)
  }

  const token = jwt.sign(
    {
      id: user.id,
      username: user.username,
    },
    SECRET,
    { expiresIn: '1 hour' }
  )

  return res.status(200).send({ access_token: token })
})

Ahora ve a Postman y usa el siguiente body dentro de la llamada de login:

{
  "username": "clarkKent",
  "password": "superman"
}

La respuesta será un token con validez definida y las informaciones que colocamos previamente:

Al copiar el token presente en el access_token y pegarlo en jwt.io tenemos el siguiente resultado:

¿Recuerdas que la ruta /secret estaba dando error? Bueno, ahora vamos a postman, en la parte de Headers y vamos a colocar un nuevo header llamado Authorization con el prefijo Bearer y el token:

Conclusión

Comparado con otros tokens web como SWTs (Simple Web Tokens) o SAML (Security Assertion Markup Language), el JWT es mucho más simple, ya que está basado en JSON, que es más fácil de entender que XML. Si codificamos el JSON, será aún más pequeño en tamaño que SAML, facilitando la transmisión en ambientes HTML y HTTP.

En términos de seguridad, los SWTs usan una única clave, mientras que JWT y SAML usan un par de claves pública y privada para mejor autenticación. Desde el punto de vista de uso, los JWTs se usan a escala de Internet. Esto significa que es más fácil procesarlos en los dispositivos del usuario, sean notebooks o celulares.

Además de la autenticación, el JSON Web Token es una manera excelente y segura de transmitir datos entre varias partes. El hecho de que los JWTs tengan firmas facilita la identificación de todos los remitentes de información. Todo lo que necesitas es la clave correcta.

Gracias por leer hasta aquí, espero que este artículo te haya sido útil.