Tokens JWT Explicados: Autenticación para Aplicaciones Web Modernas

· 12 min de lectura

Tabla de Contenidos

Los JSON Web Tokens (JWT) se han convertido en el estándar de facto para la autenticación en aplicaciones web modernas. Proporcionan una forma compacta y segura para URL de representar reclamaciones entre dos partes, permitiendo autenticación sin estado que escala horizontalmente sin almacenamiento de sesión compartido.

Ya sea que estés construyendo una API REST, una arquitectura de microservicios o una aplicación de página única, entender los JWTs es esencial para implementar autenticación segura y escalable. Esta guía completa explica cómo funcionan los JWTs, su estructura, consideraciones de seguridad y mejores prácticas listas para producción.

¿Qué es JWT?

JWT (pronunciado "jot") es un estándar abierto (RFC 7519) que define un formato compacto y autocontenido para transmitir información de forma segura entre partes como un objeto JSON. La información está firmada digitalmente usando una clave secreta (HMAC) o un par de claves pública/privada (RSA o ECDSA), asegurando la integridad y autenticidad del token.

A diferencia de la autenticación tradicional basada en sesiones donde el servidor mantiene el estado de la sesión en memoria o una base de datos, los JWTs no tienen estado. Toda la información necesaria para verificar la identidad del usuario está contenida dentro del token mismo.

Casos de Uso Principales para JWT

Consejo profesional: Usa nuestro Decodificador JWT para inspeccionar y validar tokens durante el desarrollo. Te ayuda a entender la estructura y detectar problemas comunes antes de que lleguen a producción.

Desglose de la Estructura JWT

Un JWT consiste en tres partes distintas separadas por puntos (.): encabezado.carga.firma

Así es como se ve un JWT completo:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkphbmUgRGV2ZWxvcGVyIiwiZW1haWwiOiJqYW5lQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNzEwNTkwNDAwLCJleHAiOjE3MTA2NzY4MDB9.4Adcj0mYZ8s5vxjKvV8pF7jKX9s8vZ5xJ3kL9mN2pQ4

Parte 1: Encabezado

El encabezado típicamente contiene dos campos que describen las operaciones criptográficas del token:

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

Este objeto JSON está codificado en Base64Url para formar la primera parte del JWT. La codificación lo hace seguro para URL y compacto para transmisión en encabezados HTTP.

Parte 2: Carga

La carga contiene reclamaciones—declaraciones sobre el usuario y metadatos adicionales. Las reclamaciones se categorizan en tres tipos:

{
  "sub": "1234567890",
  "name": "Jane Developer",
  "email": "[email protected]",
  "role": "admin",
  "iat": 1710590400,
  "exp": 1710676800
}

Reclamaciones registradas están predefinidas por la especificación JWT y proporcionan información estándar:

Reclamaciones públicas son reclamaciones personalizadas que deben definirse en el Registro IANA de JSON Web Token o usar nombres resistentes a colisiones (como URLs).

Reclamaciones privadas son reclamaciones personalizadas acordadas entre partes, como role, permissions, o organizationId.

Importante: La carga solo está codificada en Base64Url, no encriptada. Nunca almacenes información sensible como contraseñas, números de tarjetas de crédito o números de seguro social en una carga JWT.

Parte 3: Firma

La firma asegura que el token no ha sido alterado. Se crea tomando el encabezado codificado, la carga codificada, una clave secreta y aplicando el algoritmo especificado en el encabezado:

HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

La firma permite al destinatario verificar que el remitente es quien dice ser y que el mensaje no fue cambiado en el camino.

Comparación de Algoritmos de Firma

Algoritmo Tipo Nivel de Seguridad Caso de Uso Gestión de Claves
HS256 Simétrico (HMAC) Bueno Servicio único, APIs internas Secreto compartido
RS256 Asimétrico (RSA) Muy Bueno Múltiples servicios, APIs públicas Par de claves pública/privada
ES256 Asimétrico (ECDSA) Excelente Requisitos de alta seguridad Par de claves pública/privada
PS256 Asimétrico (RSA-PSS) Excelente Aplicaciones modernas Par de claves pública/privada

Cómo Funciona la Autenticación JWT

Entender el flujo completo de autenticación te ayuda a implementar JWTs correctamente y solucionar problemas de manera efectiva.

El Flujo Completo de Autenticación

  1. Inicio de Sesión del Usuario: El cliente envía credenciales (nombre de usuario/contraseña) al punto final de autenticación
  2. Verificación de Credenciales: El servidor valida las credenciales contra la base de datos
  3. Generación de Token: Tras autenticación exitosa, el servidor crea un JWT que contiene información del usuario y reclamaciones
  4. Entrega de Token: El servidor envía el JWT de vuelta al cliente (típicamente en el cuerpo de la respuesta)
  5. Almacenamiento de Token: El cliente almacena el JWT (en memoria, localStorage o cookies httpOnly)
  6. Solicitudes Autenticadas: Para solicitudes posteriores, el cliente incluye el JWT en el encabezado Authorization
  7. Verificación de Token: El servidor valida la firma JWT y verifica la expiración
  8. Acceso Concedido: Si es válido, el servidor procesa la solicitud usando las reclamaciones en el token

Ejemplo de Solicitud de Autenticación

POST /api/auth/login HTTP/1.1
Host: api.example.com
Content-Type: application/json

{
  "email": "[email protected]",
  "password": "securePassword123"
}

Ejemplo de Respuesta de Autenticación

HTTP/1.1 200 OK
Content-Type: application/json

{
  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "expiresIn": 3600,
  "tokenType": "Bearer"
}

Ejemplo de Solicitud de API Autenticada

GET /api/users/profile HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

JWT vs Autenticación Basada en Sesiones

Elegir entre JWT y autenticación basada en sesiones depende de la arquitectura, escala y requisitos de tu aplicación.

Aspecto JWT Basada en Sesiones
Estado Sin estado (el servidor no almacena tokens) Con estado (el servidor almacena datos de sesión)
Escalabilidad Excelente (no se necesita almacenamiento compartido) Requiere almacén de sesiones (Redis, base de datos)
Revocación Difícil (requiere lista negra o expiración corta) Fácil (eliminar sesión del almacén)
Tamaño Más grande (enviado con cada solicitud) Más pequeño (solo ID de sesión)
Entre dominios Fácil (compatible con CORS) Complejo (restricciones de dominio de cookies)
Aplicaciones móviles Ideal (no se necesita soporte de cookies) Desafiante (manejo de cookies)
Seguridad Requiere implementación cuidadosa Patrones bien establecidos

Cuándo Usar JWT

Cuándo Usar Sesiones

Implementando JWT en tu Aplicación

Veamos ejemplos de implementación práctica en lenguajes de programación y frameworks populares.

Node.js con Express

const jwt = require('jsonwebtoken');
const express = require('express');
const app = express();

const SECRET_KEY = process.env.JWT_SECRET;

// Punto final de inicio de sesión
app.post('/api/auth/login', async (req, res) => {
  const { email, password } = req.body;
  
  // Verificar credenciales (pseudo-código)
  const user = await verifyCredentials(email, password);
  
  if (!user) {
    return res.status(401).json({ error: 'Credenciales inválidas' });
  }
  
  // Generar JWT
  const token = jwt.sign(
    {
      sub: user.id,
      email: user.email,
      role: user.role
    },
    SECRET_KEY,
    { expiresIn: '1h' }
  );
  
  res.json({ accessToken: token });
});

// Middleware de autenticación
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'No se proporcionó token' });
  }
  
  jwt.verify(token, SECRET_KEY, (err, user) => {
    if (err) {
      return res.status(403).json({ error: 'Token inválido' });
    }
    req.user = user;
    next();
  });
};

// Ruta protegida
app.get('/api/users/profile', authenticateToken, (req, res) => {
  res.json({ user: req.user });
});

Python con Flask

from flask import Flask, request, jsonify
import jwt
import datetime
import os

app = Flask(__name__)
SECRET_KEY = os.getenv('JWT_SECRET')

@app.route('/api/auth/login', methods=['POST'])
def login():
    data = request.get_json()
    email = data.get('email')
    password = data.get('password')
    
    # Verificar credenciales
    user = verify_credentials(email, password)
    
    if not user:
        return jsonify({'error': 'Credenciales inválidas'}), 401
    
    # Generar JWT
    token = jwt.encode({
        'sub': user['id'],
        'email': user['email'],
        'role': user['role'],
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1)
    }, SECRET_KEY, algorithm='HS256')
    
    return jsonify({'accessToken': token})

def token_required(f):
    def decorator(*args, **kwargs):
        token = request.headers.get('Authorization')
        
        if not token:
            return jsonify({'error': 'No se proporcionó token'}), 401
        
        try:
            token = token.split(' ')[1]
            data = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
            request.user = data
        except jwt.ExpiredSignatureError:
            return jsonify({'error': 'Token expirado'}), 403
        except jwt.InvalidTokenError:
            return jsonify({'error': 'Token inválido'}), 403
        
        return f(*args, **kwargs)
    
    decorator.__name__ = f.__name__
    return decorator

@app.route('/api/users/profile')
@token_required
def profile():
    return jsonify({'user': request.user})

Consejo rápido: Siempre usa variables de entorno para claves secretas. Nunca las codifiques directamente en tu código fuente ni las confirmes en control de versiones. Usa herramientas como Generador de Contraseñas para crear secretos fuertes.

We use cookies for analytics. By continuing, you agree to our Privacy Policy.