Autenticación JWT: Cómo Funcionan los JSON Web Tokens
· 12 min de lectura
Tabla de Contenidos
- ¿Qué es un JSON Web Token?
- Estructura de JWT Explicada
- Cómo Funciona la Autenticación JWT
- Tokens de Acceso vs Tokens de Actualización
- Guía de Implementación en Node.js
- Dónde Almacenar JWTs de Forma Segura
- Mejores Prácticas de Seguridad
- Vulnerabilidades Comunes de JWT
- Elegir el Algoritmo Correcto
- JWT en Sistemas Distribuidos
- Preguntas Frecuentes
- Artículos Relacionados
¿Qué es un JSON Web Token?
Un JSON Web Token (JWT) es una forma compacta y segura para URL de representar reclamaciones entre dos partes. Los JWTs se han convertido en el estándar de facto para autenticación y autorización en aplicaciones web modernas, APIs y arquitecturas de microservicios.
Cuando un usuario inicia sesión, el servidor genera un JWT que contiene información del usuario y lo envía al cliente. El cliente luego incluye este token en solicitudes posteriores para probar su identidad. Piénsalo como un pasaporte digital que contiene tus credenciales y puede ser verificado sin llamar de vuelta a la autoridad emisora cada vez.
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 son sin estado. Toda la información necesaria para verificar el token está contenida dentro del token mismo. Esta decisión arquitectónica trae varias ventajas:
- Escalabilidad: No se necesita almacenamiento de sesiones o sesiones persistentes en entornos con balanceo de carga
- Autenticación entre dominios: Los JWTs funcionan sin problemas a través de diferentes dominios y servicios
- Compatible con móviles: Perfecto para aplicaciones móviles que necesitan autenticarse con APIs backend
- Microservicios: Cada servicio puede verificar tokens de forma independiente sin un almacén de sesiones central
- Rendimiento: Elimina búsquedas en base de datos para cada solicitud autenticada
Puedes inspeccionar y decodificar cualquier JWT usando nuestra herramienta de Decodificador JWT para ver su contenido sin necesidad de escribir código. Esto es particularmente útil al depurar problemas de autenticación o entender qué datos contienen tus tokens.
Consejo profesional: Los JWTs están firmados, no cifrados por defecto. Cualquiera puede decodificar y leer el contenido de un JWT. Nunca almacenes información sensible como contraseñas, números de tarjetas de crédito o números de seguridad social en las cargas útiles de JWT.
Estructura de JWT Explicada
Un JWT consiste en tres partes distintas separadas por puntos (.): el Encabezado, la Carga Útil y la Firma. Así es como se ve un JWT real:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiZW1haWwiOiJqb2huQGV4YW1wbGUuY29tIiwicm9sZSI6ImFkbWluIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE1MTYyNDI2MjJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Desglosemos cada componente para entender cómo trabajan juntos para crear un token seguro y verificable.
1. Encabezado
El encabezado típicamente contiene dos campos críticos que definen cómo debe procesarse el token:
{
"alg": "HS256",
"typ": "JWT"
}
alg(algoritmo): Especifica el algoritmo criptográfico usado para firmar el tokentyp(tipo): Declara que esto es un token JWT
Este objeto JSON luego se codifica 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.
2. Carga Útil
La carga útil contiene reclamaciones — declaraciones sobre el usuario y metadatos adicionales. Aquí es donde almacenas los datos reales que deseas transmitir:
{
"sub": "1234567890",
"name": "John Doe",
"email": "[email protected]",
"role": "admin",
"iat": 1516239022,
"exp": 1516242622
}
Las reclamaciones se dividen en tres categorías:
Reclamaciones registradas (estandarizadas y recomendadas):
sub(sujeto): Identificador único para el usuarioiat(emitido en): Marca de tiempo cuando se creó el tokenexp(expiración): Marca de tiempo cuando expira el tokeniss(emisor): Identifica quién emitió el tokenaud(audiencia): Identifica para quién está destinado el tokennbf(no antes de): El token no es válido antes de esta marca de tiempojti(ID de JWT): Identificador único para el token mismo
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 las partes, como role, permissions o department.
3. Firma
La firma es lo que hace que los JWTs sean seguros y a prueba de manipulaciones. Se crea tomando el encabezado codificado, la carga útil codificada, una clave secreta y aplicando el algoritmo especificado en el encabezado:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
La firma cumple dos propósitos críticos:
- Verificación de integridad: Asegura que el token no ha sido modificado desde que fue emitido
- Autenticación: Prueba que el token fue creado por alguien con acceso a la clave secreta
Si alguien intenta modificar el encabezado o la carga útil, la verificación de la firma fallará y el token será rechazado.
Cómo Funciona la Autenticación JWT
Entender el flujo completo de autenticación es esencial para implementar JWTs correctamente. Aquí está el proceso paso a paso:
Paso 1: Inicio de Sesión del Usuario
El usuario envía sus credenciales (nombre de usuario y contraseña) al endpoint de autenticación. El servidor valida estas credenciales contra la base de datos.
Paso 2: Generación del Token
Si las credenciales son válidas, el servidor genera un JWT que contiene información del usuario y reclamaciones. El servidor firma el token usando una clave secreta (para algoritmos simétricos) o una clave privada (para algoritmos asimétricos).
Paso 3: Entrega del Token
El servidor envía el JWT de vuelta al cliente, típicamente en el cuerpo de la respuesta. El cliente almacena este token para solicitudes futuras.
Paso 4: Solicitudes Autenticadas
Para solicitudes posteriores, el cliente incluye el JWT en el encabezado Authorization usando el esquema Bearer:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Paso 5: Verificación del Token
El servidor recibe la solicitud, extrae el JWT del encabezado y verifica la firma usando la clave secreta o pública. Si es válido, el servidor extrae la información del usuario de la carga útil y procesa la solicitud.
Paso 6: Expiración del Token
Cuando el token expira, el cliente debe obtener un nuevo token, ya sea reautenticándose o usando un token de actualización (cubierto en la siguiente sección).
Consejo rápido: Siempre valida la reclamación exp en el lado del servidor, incluso si verificas la expiración en el cliente. Las verificaciones del lado del cliente pueden ser evadidas, pero la validación del lado del servidor es autoritativa.
Tokens de Acceso vs Tokens de Actualización
Un sistema robusto de autenticación JWT típicamente usa dos tipos de tokens trabajando juntos: tokens de acceso y tokens de actualización. Entender la distinción es crucial para construir aplicaciones seguras.
Tokens de Acceso
Los tokens de acceso son JWTs de corta duración (típicamente 15 minutos a 1 hora) que otorgan acceso a recursos protegidos. Contienen identidad del usuario y permisos necesarios para autorizar solicitudes.
Características:
- Tiempo de expiración corto (5-60 minutos)
- Incluido en cada solicitud de API
- Contiene permisos y roles del usuario
- No puede ser revocado antes de la expiración (sin estado)
Tokens de Actualización
Los tokens de actualización son tokens de larga duración (días a meses) usados exclusivamente para obtener nuevos tokens de acceso. Se almacenan de forma segura y solo se envían al endpoint de actualización de tokens.
Características:
- Tiempo de expiración largo (días a meses)
- Solo se envía al endpoint de actualización
- Puede ser revocado en la base de datos
- A menudo almacenado con metadatos adicionales (dispositivo, IP, agente de usuario)
¿Por Qué Usar Ambos?
Este enfoque de doble token equilibra seguridad y experiencia del usuario:
| Aspecto | Solo Token de Acceso | Tokens de Acceso + Actualización |
|---|---|---|
| Seguridad | Menor (tokens de larga duración en riesgo) | Mayor (tokens de acceso de corta duración) |
| Experiencia del Usuario | Mejor (sin reautenticación) | Mejor (actualización de token sin interrupciones) |
| Revocación | Difícil (sin estado) | Posible (tokens de actualización en BD) |
| Sobrecarga de Red | Menor | Ligeramente mayor (solicitudes de actualización) |
| Complejidad de Implementación | Simple | Moderada |
Flujo de Actualización de Token
- El cliente detecta que el token de acceso ha expirado o está por expirar
- El cliente envía el token de actualización al endpoint
/auth/refresh - El servidor valida el token de actualización contra la base de datos
- El servidor genera un nuevo token de acceso (y opcionalmente un nuevo token de actualización)
- El cliente recibe y almacena los nuevos tokens
- El cliente reintenta la solicitud original con el nuevo token de acceso
Consejo profesional: Implementa rotación de tokens de actualización — emite un nuevo token de actualización cada vez que se use uno e invalida el antiguo. Esto limita el daño si un token de actualización se ve comprometido.
Guía de Implementación en Node.js
Construyamos un sistema completo de autenticación JWT usando Node.js, Express y la biblioteca jsonwebtoken. Esta implementación incluye tanto tokens de acceso como de actualización.
Instalación
npm install express jsonwebtoken bcrypt dotenv
Configuración del Entorno
Crea un archivo .env con tus claves secretas:
ACCESS_TOKEN_SECRET=tu-clave-de-acceso-super-secreta-cambia-esto
REFRESH_TOKEN_SECRET=tu-clave-de-actualizacion-super-secreta-cambia-esto
ACCESS_TOKEN_EXPIRY=15m
REFRESH_TOKEN_EXPIRY=7d
Generación de Tokens
const jwt = require('jsonwebtoken');
function generateAccessToken(user) {
return jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
process.env.ACCESS_TOKEN_SECRET,
{ expiresIn: process.env.ACCESS_TOKEN_EXPIRY }
);
}
function generateRefreshToken(user) {
return jwt.sign(
{ userId: user.id },
process.env.REFRESH_TOKEN_SECRET,
{ expiresIn: process.env.REFRESH_TOKEN_EXPIRY }
);
}
Endpoint de Inicio de Sesión
const bcrypt = require('bcrypt');
app.post('/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
// Buscar usuario en la base de datos
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
// Verificar contraseña
const validPassword = await bcrypt.compare(password, user.password);
if (!validPassword) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
// Generar tokens
const accessToken = generateAccessToken(user);
const refreshToken = generateRefreshToken(user);
// Almacenar token de actualización en la base de datos
await RefreshToken.create({
token: refreshToken,
userId: user.id,
expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
});
res.json({
accessToken,
refreshToken,
user: {
id: user.id,
email: user.email,
name: user.name
}
});
} catch (error) {
res.status(500).json({ error: 'Error del servidor' });
}
});
Middleware de Autenticación
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Token de acceso requerido' });
}
jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Token inválido o expirado' });
}
req.user = user;
next();
});
}
// Uso
app.get('/api/protected', authenticateToken, (req, res) => {
res.json({
message: 'Datos protegidos',
user: req.user
});
});
Endpoint de Actualización de Token
app.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
if (!refreshToken) {
return res.status(401).json({ error: 'Token de actualización requerido' });
}
try {
// Verificar que el token de actualización existe en la base de datos
const storedToken = await RefreshToken.findOne({ token: refreshToken });
if (!storedToken) {
return res.status(403).json({ error: 'Token de actualización inválido' });
}
// Verificar firma del token
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, async (err, user) => {
if (err) {
return res.status(403).json({ error: 'Token de actualización inválido' });
}
// Obtener datos del usuario
const userData = await User.findById(user.userId);
// Generar nuevo token de acceso
const accessToken = generateAccessToken(userData);
res.json({ accessToken });