Maneja la autorización de usuarios con JWT

En este artículo aprenderás:

  1. ¿Qué es JWT?
  2. ¿Cómo se Utiliza un JWT?
  3. Configuración del Servidor
  4. Login desde el cliente
  5. Uso del token desde el cliente

¿Qué es JWT?

JSON Web Token (JWT) es un token (una cadena de texto) utilizado comunmente para la autenticación y la autorización de usuarios. El token es una cadena de texto codificada en Base 64 que consiste en 3 componentes.

  1. Header: El header del token contiene información sobre el algoritmo de cifrado utilizado para codificar el token.
  2. Payload: El payload del token contiene información sobre el usuario (por ejemplo, el nombre de usuario, id de usuario, etc.).
  3. Signature: La firma del token es un hash generado a partir de una llave privada que existe en el servidor utilizando el algoritmo de cifrado especificado en el header.
Ejemplo de un JWT:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJub21icmUiOiJTZWJhc3RpYW4gRmVybmFuZGV6IiwiaWQiOiIxIiwibWVuc2FqZSI6IlNpIGRlY29kaWZpY2FzdGUgZXN0ZSB0b2tlbiwgdGUgZGVzZW8gdW4gZXhjZWxlbnRlIGTDrWEhIiwiaWF0IjoxNTE2MjM5MDIyfQ.VImHpjsLk3SwJ5qOhfLYOlBo1_MOOi7d1PQxrNVm3oA

El token está dividido en 3 partes separadas por un punto (.) y cada parte está codificada en Base 64.

  1. Header:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

  2. Payload:

    eyJub21icmUiOiJTZWJhc3RpYW4gRmVybmFuZGV6IiwiaWQiOiIxIiwibWVuc2FqZSI6IlNpIGRlY29kaWZpY2FzdGUgZXN0ZSB0b2tlbiwgdGUgZGVzZW8gdW4gZXhjZWxlbnRlIGTDrWEhIiwiaWF0IjoxNTE2MjM5MDIyfQ.

  3. Signature:

    VImHpjsLk3SwJ5qOhfLYOlBo1_MOOi7d1PQxrNVm3oA

El payload del JWT generalmente contiene información sobre el usuario (por ejemplo, el nombre de usuario, el id de usuario, etc.). Una vez que el token es decodificado, este se puede leer en formato JSON.

¿Cómo se Utiliza un JWT?

Si intentaste decodificar el token anterior con base 64 (puedes decodificarlo aquí), verás que el payload del token es un objeto JSON.

Pero... si cualquier persona puede decodificar el token, ¿Por qué es seguro?

La respuesta es simple, la tercera parte del token que contiene la firma del servidor es lo que lo hace seguro. El servidor tiene una llave secreta con la que genera la firma del token. El token es enviado al cliente y normalmente se almacena en el localStorage del navegador. Cada vez que el cliente hace una petición al servidor, el token es enviado en el header de 'Authorization' de la petición . El servidor verifica la firma del token utilizando la llave privada y si la firma es válida, el servidor procesa la petición. Si algo en el token fue cambiado o alterado, la firma del token no será válida y el servidor rechazará la petición.

Configuración del Servidor

Inicializamos el proyecto con NPM

npm init -y

Instalamos las dependencias que vamos a necesitar

npm i express jsonwebtoken

Archivo de servidor

// server.js
const express = require("express");
const app = express();
const PORT = 3000;
const router = require("./router");

app.use(express.json());

app.use(router);

app.listen(PORT, () => {
  console.log(`Servidor corriendo en el puerto ${PORT}...`);
});

Archivo de rutas

// router.js
const router = require("express").Router();
const login = require("./handlers/login");
const getInfo = require("./handlers/getInfo");

// Endpoints publicos
router.post("/login", login);

// Endpoints privados
router.get("/info-super-secreta", getInfo);

module.exports = router;

Handler de Login

En este artículo no vamos a ver cómo autenticar a un usuario (puedes ver mi artículo de autenticación con google aquí), pero vamos a simularlo con una función.

// handlers/login.js
const jwt = require("jsonwebtoken");

const createJWT = (user) => {
  const token = jwt.sign(
    { id: user.id, username: user.username },
    "LLAVE-SUPER-SECRETA-123", // La llave secreta normalmente se almacena en un archivo .env para mantenerla segura.
    {
      expiresIn: "21d",
    }
  );
  return token;
};

const login = (req, res) => {
  const { username, password } = req.body;
  const user = authenticateUser(username, password); // Función de autenticación
  // Simulamos que el usuario se autentico correctamente
  const authenticatedUser = { id: 1, username: "sebastian" };
  const token = createJWT(authenticatedUser);
  res.json({ token });
};

module.exports = login;

Handler de Información Super Secreta

// handlers/getInfo.js
const jwt = require("jsonwebtoken");

const notAuthorized = (res) => {
  return res.status(401).json({ message: "Not authorized" });
};

const invalidToken = (res) => {
  return res.status(401).json({ message: "Invalid token" });
};

const getInfo = (req, res) => {
  // Los tokens JWT normalmente llevan el nombre de Bearer Tokens y van en el header de la petición con la palabra Bearer seguido del token.
  const bearer = req.headers?.authorization;

  // Si no hay un header de authorization, el usuario no está autorizado.
  if (!bearer) {
    notAuthorized(res);
    return;
  }

  const [, token] = bearer?.split(" "); // Separamos la palabra 'Bearer' del token

  // Si no hay un token, el usuario no está autorizado.
  if (!token) {
    notAuthorized(res);
    return;
  }
  try {
    // Verificamos el token con la llave secreta del servidor. Si el token el invalido, la función se irá al catch. Si el token es válido, devolverá el payload del token.
    const user = jwt.verify(token, "LLAVE-SUPER-SECRETA-123"); // La llave secreta normalmente se almacena en un archivo .env para mantenerla segura.

    res.send(`Hola ${user.username}! Esta es tu información super secreta!`);
    res.status(200);
  } catch (err) {
    invalidToken(res);
    return;
  }
};

Petición desde el Cliente (Login)

Para hacer la petición desde el cliente vamos a utilizar fetch. En este ejemplo vamos a hacer una petición POST al endpoint de login y guardar el token en el localStorage del navegador.

Petición desde el Cliente (Info Secreta)

En este ejemplo vamos a hacer una petición GET al endpoint de info secreta y enviar el token en el header 'Authorization' de la petición.

Ayúdame a mejorar este artículo

¿Quisieras complementar este artículo o encontraste algún error?¡Excelente! Envíame un correo.

  • seb@sebastianfdz.com