Tokens JWT Explicados: Autenticación para Aplicaciones Web Modernas
· 12 min de lectura
Tabla de Contenidos
- ¿Qué es JWT?
- Desglose de la Estructura JWT
- Cómo Funciona la Autenticación JWT
- JWT vs Autenticación Basada en Sesiones
- Implementando JWT en tu Aplicación
- Mejores Prácticas de Seguridad
- Vulnerabilidades Comunes y Cómo Prevenirlas
- Estrategias de Renovación de Tokens
- Dónde Almacenar JWTs
- Consideraciones de Rendimiento
- Preguntas Frecuentes
- Artículos Relacionados
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
- Autenticación: Después de que un usuario inicia sesión, el servidor emite un JWT que el cliente incluye con solicitudes posteriores para probar su identidad
- Autorización: Los JWTs pueden llevar roles de usuario, permisos y alcances, permitiendo a los servidores tomar decisiones de control de acceso granular sin consultas a la base de datos
- Intercambio de información: La naturaleza firmada de los JWTs asegura que el remitente es quien dice ser y que los datos no han sido alterados durante la transmisión
- Inicio de Sesión Único (SSO): Los JWTs permiten autenticación sin problemas a través de múltiples dominios y servicios
- Autenticación de API: Las aplicaciones móviles e integraciones de terceros pueden autenticar solicitudes de API sin mantener sesiones del lado del servidor
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"
}
algespecifica el algoritmo de firma (HS256, RS256, ES256, etc.)typdeclara el tipo de token, que siempre es "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:
iss(emisor): Identifica quién emitió el tokensub(sujeto): Identifica el sujeto del token (usualmente el ID de usuario)aud(audiencia): Identifica los destinatarios previstosexp(tiempo de expiración): Marca de tiempo Unix cuando el token expiranbf(no antes de): El token no es válido antes de este tiempoiat(emitido en): Cuándo se creó el tokenjti(ID JWT): Identificador único para el token
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
- Inicio de Sesión del Usuario: El cliente envía credenciales (nombre de usuario/contraseña) al punto final de autenticación
- Verificación de Credenciales: El servidor valida las credenciales contra la base de datos
- Generación de Token: Tras autenticación exitosa, el servidor crea un JWT que contiene información del usuario y reclamaciones
- Entrega de Token: El servidor envía el JWT de vuelta al cliente (típicamente en el cuerpo de la respuesta)
- Almacenamiento de Token: El cliente almacena el JWT (en memoria, localStorage o cookies httpOnly)
- Solicitudes Autenticadas: Para solicitudes posteriores, el cliente incluye el JWT en el encabezado Authorization
- Verificación de Token: El servidor valida la firma JWT y verifica la expiración
- 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
- Construyendo microservicios que necesitan compartir autenticación
- Desarrollando aplicaciones móviles o SPAs
- Creando APIs públicas para integraciones de terceros
- Implementando Inicio de Sesión Único (SSO) a través de múltiples dominios
- Escalando horizontalmente sin almacenamiento de sesión compartido
Cuándo Usar Sesiones
- Construyendo aplicaciones web tradicionales renderizadas en el servidor
- Requiriendo capacidades de revocación inmediata de tokens
- Trabajando con datos sensibles que necesitan control del lado del servidor
- Minimizando ancho de banda (las sesiones usan cookies más pequeñas)
- Implementando características complejas de gestión de 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.