← Volver al Blog

JSON Web Tokens (JWT): Autenticación y Autorización Completa

Aprende a implementar autenticación con JWT. Incluye ejemplos en Node.js, Python, seguridad y mejores prácticas.

Big JSON Team14 min de lecturaSeguridad
B

Big JSON Team

Technical Writer

Expert in JSON data manipulation, API development, and web technologies. Passionate about creating tools that make developers' lives easier.

14 min lectura

# JSON Web Tokens (JWT): Autenticación y Autorización Completa

JWT es el estándar de facto para autenticación en aplicaciones modernas. Esta guía completa te enseñará todo sobre JWT, desde conceptos básicos hasta implementación segura.

¿Qué es JWT?

JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define una forma compacta y autónoma de transmitir información de forma segura entre partes como un objeto JSON.

Estructura de un JWT

Un JWT consta de tres partes separadas por puntos:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

[Header].[Payload].[Signature]

1. Header (Cabecera)

{

"alg": "HS256",

"typ": "JWT"

}

Codificado en Base64URL: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

2. Payload (Carga útil)

{

"sub": "1234567890",

"name": "John Doe",

"iat": 1516239022,

"exp": 1516242622,

"role": "admin"

}

Codificado en Base64URL: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ

3. Signature (Firma)

HMACSHA256(

base64UrlEncode(header) + "." +

base64UrlEncode(payload),

secret

)

Claims Estándar del Payload

Registered Claims (Recomendados)

{

"iss": "https://mi-app.com", // Issuer (emisor)

"sub": "user123", // Subject (sujeto/usuario)

"aud": "https://api.mi-app.com", // Audience (audiencia)

"exp": 1735689600, // Expiration (expiración)

"nbf": 1735686000, // Not Before (no antes de)

"iat": 1735686000, // Issued At (emitido en)

"jti": "abc123" // JWT ID (identificador único)

}

Custom Claims (Personalizados)

{

"userId": "12345",

"email": "usuario@ejemplo.com",

"role": "admin",

"permissions": ["read", "write", "delete"],

"tenant": "empresa-xyz"

}

Implementación en Node.js

Instalación

npm install jsonwebtoken

Generar Token

const jwt = require('jsonwebtoken');

const SECRET_KEY = process.env.JWT_SECRET || 'tu-clave-secreta-muy-segura';

function generarToken(usuario) {

const payload = {

sub: usuario.id,

email: usuario.email,

role: usuario.role,

iat: Math.floor(Date.now() / 1000),

exp: Math.floor(Date.now() / 1000) + (60 60) // 1 hora

};

return jwt.sign(payload, SECRET_KEY);

}

// Uso

const usuario = {

id: '12345',

email: 'ana@ejemplo.com',

role: 'admin'

};

const token = generarToken(usuario);

console.log(token);

Opciones Avanzadas

const token = jwt.sign(

{ userId: usuario.id },

SECRET_KEY,

{

algorithm: 'HS256',

expiresIn: '1h', // 1 hora

issuer: 'mi-app',

audience: 'api.mi-app.com',

subject: usuario.id.toString(),

notBefore: '0', // Válido inmediatamente

jwtid: generarUUID() // ID único del token

}

);

Verificar Token

function verificarToken(token) {

try {

const decoded = jwt.verify(token, SECRET_KEY);

return {

valido: true,

datos: decoded

};

} catch (error) {

if (error.name === 'TokenExpiredError') {

return { valido: false, error: 'Token expirado' };

} else if (error.name === 'JsonWebTokenError') {

return { valido: false, error: 'Token inválido' };

} else {

return { valido: false, error: 'Error de verificación' };

}

}

}

// Uso

const resultado = verificarToken(token);

if (resultado.valido) {

console.log('Usuario:', resultado.datos);

} else {

console.error('Error:', resultado.error);

}

Decodificar Sin Verificar

// Solo para inspeccionar, NO para validar

const decoded = jwt.decode(token, { complete: true });

console.log('Header:', decoded.header);

console.log('Payload:', decoded.payload);

console.log('Signature:', decoded.signature);

Middleware de Autenticación (Express)

Middleware Básico

const jwt = require('jsonwebtoken');

function autenticar(req, res, next) {

// Obtener token del header

const authHeader = req.headers['authorization'];

const token = authHeader && authHeader.split(' ')[1]; // "Bearer TOKEN"

if (!token) {

return res.status(401).json({ error: 'Token no proporcionado' });

}

try {

const decoded = jwt.verify(token, process.env.JWT_SECRET);

req.usuario = decoded; // Agregar datos a request

next();

} catch (error) {

return res.status(403).json({ error: 'Token inválido' });

}

}

// Uso en rutas

app.get('/api/perfil', autenticar, (req, res) => {

// req.usuario contiene los datos del token

res.json({

mensaje: 'Perfil del usuario',

usuario: req.usuario

});

});

Middleware con Roles

function requiereRol(...rolesPermitidos) {

return (req, res, next) => {

if (!req.usuario) {

return res.status(401).json({ error: 'No autenticado' });

}

if (!rolesPermitidos.includes(req.usuario.role)) {

return res.status(403).json({

error: 'No tienes permisos para esta acción'

});

}

next();

};

}

// Uso

app.delete('/api/usuarios/:id',

autenticar,

requiereRol('admin', 'superadmin'),

(req, res) => {

// Solo admin o superadmin pueden ejecutar esto

eliminarUsuario(req.params.id);

res.json({ mensaje: 'Usuario eliminado' });

}

);

Sistema Completo de Autenticación

const express = require('express');

const jwt = require('jsonwebtoken');

const bcrypt = require('bcrypt');

const app = express();

app.use(express.json());

const SECRET_KEY = process.env.JWT_SECRET;

// Base de datos simulada

const usuarios = [];

// Registro

app.post('/api/registro', async (req, res) => {

try {

const { email, password, nombre } = req.body;

// Validar

if (!email || !password || !nombre) {

return res.status(400).json({ error: 'Datos incompletos' });

}

// Verificar si existe

if (usuarios.find(u => u.email === email)) {

return res.status(400).json({ error: 'Email ya registrado' });

}

// Hash password

const passwordHash = await bcrypt.hash(password, 10);

// Crear usuario

const nuevoUsuario = {

id: usuarios.length + 1,

email,

nombre,

passwordHash,

role: 'usuario',

createdAt: new Date()

};

usuarios.push(nuevoUsuario);

// Generar token

const token = jwt.sign(

{

sub: nuevoUsuario.id,

email: nuevoUsuario.email,

role: nuevoUsuario.role

},

SECRET_KEY,

{ expiresIn: '7d' }

);

res.status(201).json({

mensaje: 'Usuario creado',

token,

usuario: {

id: nuevoUsuario.id,

email: nuevoUsuario.email,

nombre: nuevoUsuario.nombre

}

});

} catch (error) {

res.status(500).json({ error: 'Error en el servidor' });

}

});

// Login

app.post('/api/login', async (req, res) => {

try {

const { email, password } = req.body;

// Buscar usuario

const usuario = usuarios.find(u => u.email === email);

if (!usuario) {

return res.status(401).json({ error: 'Credenciales inválidas' });

}

// Verificar password

const passwordValido = await bcrypt.compare(password, usuario.passwordHash);

if (!passwordValido) {

return res.status(401).json({ error: 'Credenciales inválidas' });

}

// Generar token

const token = jwt.sign(

{

sub: usuario.id,

email: usuario.email,

role: usuario.role

},

SECRET_KEY,

{ expiresIn: '7d' }

);

res.json({

token,

usuario: {

id: usuario.id,

email: usuario.email,

nombre: usuario.nombre,

role: usuario.role

}

});

} catch (error) {

res.status(500).json({ error: 'Error en el servidor' });

}

});

// Ruta protegida

app.get('/api/perfil', autenticar, (req, res) => {

const usuario = usuarios.find(u => u.id === req.usuario.sub);

res.json({

id: usuario.id,

email: usuario.email,

nombre: usuario.nombre,

role: usuario.role

});

});

app.listen(3000, () => console.log('Servidor en puerto 3000'));

Implementación en Python

Instalación

pip install pyjwt cryptography

Generar y Verificar Token

import jwt

import datetime

from typing import Dict, Optional

SECRET_KEY = "tu-clave-secreta-muy-segura"

ALGORITHM = "HS256"

def generar_token(usuario: Dict) -> str:

"""Genera JWT para usuario"""

payload = {

'sub': str(usuario['id']),

'email': usuario['email'],

'role': usuario['role'],

'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1),

'iat': datetime.datetime.utcnow()

}

return jwt.encode(payload, SECRET_KEY, algorithm=ALGORITHM)

def verificar_token(token: str) -> Optional[Dict]:

"""Verifica y decodifica JWT"""

try:

payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])

return payload

except jwt.ExpiredSignatureError:

print("Token expirado")

return None

except jwt.InvalidTokenError:

print("Token inválido")

return None

# Uso

usuario = {

'id': 123,

'email': 'ana@ejemplo.com',

'role': 'admin'

}

token = generar_token(usuario)

print(f"Token: {token}")

# Verificar

datos = verificar_token(token)

if datos:

print(f"Usuario: {datos['email']}")

Flask con JWT

from flask import Flask, request, jsonify

from functools import wraps

import jwt

import datetime

app = Flask(__name__)

app.config['SECRET_KEY'] = 'tu-clave-secreta'

def token_requerido(f):

"""Decorador para rutas protegidas"""

@wraps(f)

def decorated(args, *kwargs):

token = request.headers.get('Authorization')

if not token:

return jsonify({'error': 'Token faltante'}), 401

# Remover "Bearer " si existe

if token.startswith('Bearer '):

token = token[7:]

try:

datos = jwt.decode(

token,

app.config['SECRET_KEY'],

algorithms=['HS256']

)

request.usuario_actual = datos

except jwt.ExpiredSignatureError:

return jsonify({'error': 'Token expirado'}), 401

except jwt.InvalidTokenError:

return jsonify({'error': 'Token inválido'}), 401

return f(args, kwargs)

return decorated

@app.route('/api/login', methods=['POST'])

def login():

datos = request.get_json()

email = datos.get('email')

password = datos.get('password')

# Validar credenciales (simplificado)

if email == 'usuario@ejemplo.com' and password == 'password123':

token = jwt.encode(

{

'sub': '123',

'email': email,

'role': 'usuario',

'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24)

},

app.config['SECRET_KEY'],

algorithm='HS256'

)

return jsonify({'token': token})

return jsonify({'error': 'Credenciales inválidas'}), 401

@app.route('/api/protegido', methods=['GET'])

@token_requerido

def ruta_protegida():

return jsonify({

'mensaje': 'Acceso autorizado',

'usuario': request.usuario_actual

})

if __name__ == '__main__':

app.run(debug=True)

Refresh Tokens

¿Por Qué Refresh Tokens?

  • Access Token: Corta duración (15 min - 1 hora)
  • Refresh Token: Larga duración (7 días - 30 días)

Implementación

const jwt = require('jsonwebtoken');

const ACCESS_TOKEN_SECRET = process.env.ACCESS_TOKEN_SECRET;

const REFRESH_TOKEN_SECRET = process.env.REFRESH_TOKEN_SECRET;

// Almacenar refresh tokens (usar DB en producción)

const refreshTokens = new Set();

function generarTokens(usuario) {

const accessToken = jwt.sign(

{ sub: usuario.id, email: usuario.email },

ACCESS_TOKEN_SECRET,

{ expiresIn: '15m' }

);

const refreshToken = jwt.sign(

{ sub: usuario.id },

REFRESH_TOKEN_SECRET,

{ expiresIn: '7d' }

);

refreshTokens.add(refreshToken);

return { accessToken, refreshToken };

}

// Login

app.post('/api/login', async (req, res) => {

// ... validar credenciales ...

const tokens = generarTokens(usuario);

res.json({

accessToken: tokens.accessToken,

refreshToken: tokens.refreshToken

});

});

// Refresh

app.post('/api/refresh', (req, res) => {

const { refreshToken } = req.body;

if (!refreshToken) {

return res.status(401).json({ error: 'Refresh token requerido' });

}

if (!refreshTokens.has(refreshToken)) {

return res.status(403).json({ error: 'Refresh token inválido' });

}

try {

const decoded = jwt.verify(refreshToken, REFRESH_TOKEN_SECRET);

// Generar nuevo access token

const accessToken = jwt.sign(

{ sub: decoded.sub },

ACCESS_TOKEN_SECRET,

{ expiresIn: '15m' }

);

res.json({ accessToken });

} catch (error) {

return res.status(403).json({ error: 'Refresh token expirado' });

}

});

// Logout

app.post('/api/logout', (req, res) => {

const { refreshToken } = req.body;

refreshTokens.delete(refreshToken);

res.json({ mensaje: 'Logout exitoso' });

});

Seguridad

1. Usa Algoritmos Seguros

// ✅ Recomendado

const token = jwt.sign(payload, secret, { algorithm: 'HS256' });

// ✅ Aún mejor (asimétrico)

const token = jwt.sign(payload, privateKey, { algorithm: 'RS256' });

// ❌ NUNCA uses 'none'

// const token = jwt.sign(payload, '', { algorithm: 'none' });

2. Claves Secretas Fuertes

// ❌ Débil

const SECRET = 'secret';

// ✅ Fuerte (al menos 256 bits)

const SECRET = crypto.randomBytes(32).toString('hex');

// o usar variables de entorno

const SECRET = process.env.JWT_SECRET;

3. Validar Siempre el Algoritmo

// Especificar algoritmos permitidos

jwt.verify(token, secret, { algorithms: ['HS256'] });

// Previene ataques de confusión de algoritmo

4. Usar HTTPS

❌ http://api.ejemplo.com  → JWT visible en tráfico

✅ https://api.ejemplo.com → JWT encriptado en tráfico

5. Expiración Apropiada

// Tokens de corta duración

const accessToken = jwt.sign(payload, secret, { expiresIn: '15m' });

// Con refresh tokens

const refreshToken = jwt.sign(payload, secret, { expiresIn: '7d' });

6. No Almacenar Datos Sensibles

// ❌ NO hacer esto

const token = jwt.sign({

password: usuario.password,

creditCard: usuario.creditCard

}, secret);

// ✅ Solo datos necesarios no sensibles

const token = jwt.sign({

sub: usuario.id,

role: usuario.role

}, secret);

Almacenamiento en Cliente

Opciones

localStorage:

// ❌ Vulnerable a XSS

localStorage.setItem('token', token);

sessionStorage:

// ❌ También vulnerable a XSS

sessionStorage.setItem('token', token);

HttpOnly Cookies:*

// ✅ Más seguro (no accesible desde JavaScript)

res.cookie('token', token, {

httpOnly: true,

secure: true, // Solo HTTPS

sameSite: 'strict',

maxAge: 3600000 // 1 hora

});

// Servidor envía en cookie HttpOnly

res.cookie('refreshToken', refreshToken, {

httpOnly: true,

secure: true,

sameSite: 'strict'

});

// Access token en respuesta JSON

res.json({ accessToken });

// Cliente guarda access token en memoria (no localStorage)

// y envía en Authorization header

Mejores Prácticas

1. Implementa Lista Negra para Logout

const listaNegraTokens = new Set();

app.post('/api/logout', autenticar, (req, res) => {

const token = req.headers.authorization.split(' ')[1];

listaNegraTokens.add(token);

res.json({ mensaje: 'Logout exitoso' });

});

// Middleware actualizado

function autenticar(req, res, next) {

const token = req.headers.authorization?.split(' ')[1];

if (listaNegraTokens.has(token)) {

return res.status(401).json({ error: 'Token revocado' });

}

// ... resto de verificación

}

2. Rate Limiting

const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({

windowMs: 15 60 * 1000, // 15 minutos

max: 5, // 5 intentos

message: 'Demasiados intentos de login'

});

app.post('/api/login', loginLimiter, async (req, res) => {

// ... lógica de login

});

3. Auditoría

function registrarAcceso(req, accion) {

console.log({

timestamp: new Date(),

usuario: req.usuario?.sub,

accion,

ip: req.ip,

userAgent: req.get('user-agent')

});

}

app.get('/api/datos-sensibles', autenticar, (req, res) => {

registrarAcceso(req, 'ACCESO_DATOS_SENSIBLES');

// ... resto de lógica

});

Conclusión

JWT es poderoso pero debe implementarse correctamente. La seguridad es crítica.

Puntos Clave

  • Usa algoritmos seguros (HS256, RS256)
  • Implementa refresh tokens
  • Expira access tokens rápidamente
  • Almacena en HttpOnly cookies cuando sea posible
  • Siempre usa HTTPS
  • No almacenes datos sensibles en payload
  • Implementa rate limiting
  • Valida todos los tokens

¡Implementa autenticación JWT de forma segura!

Share:

Artículos Relacionados

Read in English