Imaginemos que tenemos una API REST con varias rutas. Algunas rutas pueden ser públicas, como una página de inicio o un listado general, pero otras deberían ser privadas, como el perfil de un usuario.

El problema principal sería el siguiente:

¿Cómo podemos saber si el usuario que está haciendo una petición está autenticado?

Para resolverlo, vamos a usar un token JWT. El funcionamiento será sencillo:

Antes de empezar, necesitamos tener instalado:

  • Node.js
  • npm
  • Visual Studio Code
  • Un navegador web

También es recomendable tener conocimientos básicos de JavaScript y de cómo funcionan las peticiones HTTP.

Lo primero será crear una carpeta para el proyecto e inicializar Node.js:

mkdir jwt-api-visual-demo
cd jwt-api-visual-demo
npm init -y

Esto creará el archivo package.json, donde se guardará la configuración del proyecto.

Para este ejemplo vamos a utilizar tres paquetes principales:

npm install express jsonwebtoken dotenv

Estos paquetes sirven para lo siguiente:

  • express: permite crear el servidor y las rutas de la API.
  • jsonwebtoken: permite crear y validar tokens JWT.
  • dotenv: permite trabajar con variables de entorno.

También podemos instalar nodemon para reiniciar el servidor automáticamente cada vez que hagamos un cambio:

npm install -D nodemon

Después, en el archivo package.json, añadimos este script:

"scripts": {
  "dev": "nodemon server.js"
}

Así podremos arrancar el proyecto con:

npm run dev

Creamos un archivo llamado .env en la raíz del proyecto:

JWT_SECRET=mi_clave_secreta_super_segura

Esta clave será utilizada para firmar y verificar los tokens JWT.

En un proyecto real, este archivo no debería subirse nunca a GitHub. Para evitarlo, se recomienda añadir .env al archivo .gitignore.

Creamos un archivo llamado server.js. Este archivo será el encargado de levantar el servidor, servir la página visual y definir las rutas de la API.

const express = require('express');
const jwt = require('jsonwebtoken');
require('dotenv').config();

const app = express();
const PORT = 3000;

app.use(express.json());
app.use(express.static('public'));

const userDemo = {
  id: 1,
  username: 'daniel',
  password: '1234'
};

app.get('/api/public', (req, res) => {
  res.json({
    message: 'Esta es una ruta pública. No necesitas token para acceder.'
  });
});

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

  if (username !== userDemo.username || password !== userDemo.password) {
    return res.status(401).json({
      message: 'Usuario o contraseña incorrectos'
    });
  }

  const token = jwt.sign(
    {
      id: userDemo.id,
      username: userDemo.username
    },
    process.env.JWT_SECRET,
    {
      expiresIn: '1h'
    }
  );

  res.json({
    message: 'Login correcto',
    token: token
  });
});

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return res.status(401).json({
      message: 'Acceso denegado. No se ha enviado ningún token.'
    });
  }

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) {
      return res.status(403).json({
        message: 'Token inválido o expirado'
      });
    }

    req.user = user;
    next();
  });
}

app.get('/api/profile', authenticateToken, (req, res) => {
  res.json({
    message: 'Has accedido a una ruta protegida',
    user: req.user
  });
});

app.listen(PORT, () => {
  console.log(`Servidor iniciado en http://localhost:${PORT}`);
});

Para que la demo sea más fácil de entender, he creado una pequeña página web con cuatro botones:

  • Probar ruta pública
  • Hacer login y generar token
  • Perfil sin token
  • Perfil con token

De esta forma no necesitamos usar Postman ni Thunder Client durante el vídeo. Toda la prueba se puede hacer desde el navegador.

La interfaz permite ver claramente el flujo de autenticación:

  1. Primero se prueba una ruta pública.
  2. Después se hace login y se genera un token JWT.
  3. Luego se intenta entrar a una ruta protegida sin token.
  4. Por último, se accede a la misma ruta enviando el token generado.

Al pulsar el botón “Probar ruta pública”, la aplicación llama a la ruta:

GET /api/public

La respuesta de la API será parecida a esta:

{
  "message": "Esta es una ruta pública. No necesitas token para acceder."
}

Esto demuestra que hay rutas que pueden funcionar sin autenticación.

Después pulsamos el botón “Hacer login y generar token”.

La aplicación envía al backend un usuario y contraseña de prueba:

{
  "username": "daniel",
  "password": "1234"
}

Si los datos son correctos, el backend genera un token JWT y lo devuelve al cliente.

Ese token será el que usaremos después para acceder a la ruta protegida.

Ahora pulsamos el botón “Perfil sin token”.

La aplicación intenta acceder a:

GET /api/profile

Pero no envía ningún token. Por eso, la API responde con un error:

{
  "message": "Acceso denegado. No se ha enviado ningún token."
}

Este paso es importante porque demuestra que la ruta está realmente protegida.

Por último, pulsamos el botón “Perfil con token”.

En este caso, la aplicación sí envía el token generado en el login dentro del header Authorization:

Authorization: Bearer TOKEN

El backend recibe el token, lo valida y permite el acceso a la ruta protegida.

La respuesta será parecida a esta:

{
  "message": "Has accedido a una ruta protegida",
  "user": {
    "id": 1,
    "username": "daniel"
  }
}

Uno de los problemas más habituales al trabajar con JWT es enviar mal el token. El formato correcto debe ser:

Authorization: Bearer TOKEN

Si se olvida la palabra Bearer o se copia mal el token, la API no podrá validarlo.

Otro problema común es configurar mal la variable JWT_SECRET. Si el token se firma con una clave y se intenta validar con otra distinta, la verificación fallará.

También puede ocurrir que el token haya expirado. En este ejemplo hemos configurado una duración de una hora, por lo que después de ese tiempo sería necesario iniciar sesión de nuevo.

Aunque esta demo es sencilla, en una aplicación real deberíamos tener en cuenta algunas buenas prácticas:

  • No guardar contraseñas en texto plano.
  • Cifrar las contraseñas con una librería como bcrypt.
  • No subir el archivo .env a GitHub.
  • Usar HTTPS en producción.
  • No guardar información sensible dentro del token.
  • Añadir roles de usuario si la aplicación tiene permisos diferentes.

JWT es una solución muy utilizada para proteger APIs REST modernas. Su funcionamiento consiste en generar un token cuando el usuario inicia sesión y pedir ese token cada vez que se quiera acceder a una ruta privada.

En este tutorial hemos visto una demo visual con Node.js y Express. Primero hemos probado una ruta pública, después hemos generado un token mediante login y finalmente hemos comprobado cómo una ruta protegida solo permite el acceso cuando recibe un token válido.

Aunque el ejemplo es básico, la idea principal es la misma que se utiliza en aplicaciones reales. La diferencia es que en producción habría que añadir más medidas de seguridad, como cifrado de contraseñas, HTTPS, roles de usuario y una gestión más avanzada de tokens.


Autor: Daniel Talavera Clemente
Máster: Desarrollo Full Stack + Arquitecturas Cloud
Centro: Tajamar Tech
Año académico: 2025-2026
Código / recursos utilizados : github

Leave a Comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.