@hemia/jwt-manager
v0.0.6
Published
Gestor de JWT seguro y extensible para aplicaciones Node.js
Readme
@hemia/jwt-manager
Una clase completa y robusta para la generación, validación y decodificación de tokens JWT con soporte completo para estándares OIDC (OpenID Connect) y OAuth 2.0. Ideal para gestionar autenticación y autorización basada en tokens dentro de aplicaciones Node.js modernas.
✨ Características principales
- ✅ Estándares OIDC/OAuth 2.0: Claims estándar y tokens compatibles con especificaciones oficiales
- 🔒 Seguridad mejorada: Algoritmos explícitos, validación de issuer/audience, clock skew tolerance
- 🎯 Tres tipos de tokens: ID Token, Access Token y Refresh Token
- 🔑 Gestión de permisos: Sistema de scopes para autorización granular
- 📝 Claims estándar: Soporte completo para profile, email, phone, address claims
- 🔄 Retrocompatible: Los métodos existentes siguen funcionando
- 🛡️ Manejo de errores detallado: Identificación específica de tipos de error
- 🚀 TypeScript nativo: Tipado completo y autocomplete
📦 Instalación
npm install @hemia/jwt-manager🚀 Inicio rápido
import { JwtManager } from '@hemia/jwt-manager';
const jwtManager = new JwtManager();
// Crear un ID Token (OIDC)
const idToken = jwtManager.createIdToken({
sub: 'user_12345',
name: 'Cristian Mendez',
email: '[email protected]',
email_verified: true
}, '1h');
// Crear un Access Token con permisos
const accessToken = jwtManager.createAccessToken(
'user_12345',
['read:products', 'write:orders'],
'15m'
);
// Validar token
const payload = jwtManager.verify(accessToken);
if (payload) {
console.log('✅ Token válido:', payload);
}
// Verificar permisos
const hasPermission = jwtManager.hasScope(accessToken, 'read:products');
console.log('Tiene permiso:', hasPermission);📖 API Reference
🎫 Métodos de Creación de Tokens
createIdToken(claims, expiresIn?)
Crea un ID Token según estándar OIDC con información de identidad del usuario.
const idToken = jwtManager.createIdToken({
sub: 'user_12345', // Requerido
name: 'Cristian Mendez',
email: '[email protected]',
email_verified: true,
picture: 'https://example.com/avatar.jpg',
preferred_username: 'cristianm',
locale: 'es-MX'
}, '1h');
// Token contiene:
// {
// sub, name, email, email_verified, picture, preferred_username, locale,
// iss: 'hemia-app',
// aud: 'hemia-api',
// iat: 1703779200,
// exp: 1703782800
// }createAccessToken(sub, scopes, expiresIn?)
Crea un Access Token OAuth 2.0 con scopes para autorización.
const accessToken = jwtManager.createAccessToken(
'user_12345',
['read:products', 'write:orders', 'admin:users'],
'15m' // Access tokens suelen ser de corta duración
);
// Token contiene:
// {
// sub: 'user_12345',
// scope: 'read:products write:orders admin:users',
// iss: 'hemia-app',
// aud: 'hemia-api',
// iat: 1703779200,
// exp: 1703780100
// }createRefreshToken(sub, expiresIn?)
Crea un Refresh Token para renovar access tokens expirados.
const refreshToken = jwtManager.createRefreshToken('user_12345', '30d');
// Token contiene:
// {
// sub: 'user_12345',
// type: 'refresh',
// jti: '1703779200-xyz123abc', // JWT ID único para revocación
// iss: 'hemia-app',
// aud: 'hemia-api',
// iat: 1703779200,
// exp: 1706371200
// }createToken(payload, expiresIn?, options?)
Crea un JWT genérico con payload personalizado (usa clave por defecto).
const token = jwtManager.createToken(
{ userId: 'abc123', role: 'admin' },
'2h',
{ issuer: 'my-app' } // Opcional
);createTokenWithSecret(payload, secretKey, expiresIn?, options?)
Crea un JWT genérico con una clave secreta específica.
const token = jwtManager.createTokenWithSecret(
{ data: 'sensitive' },
'custom-secret-key',
'1h'
);createCleanCredentialsToken(operative?, expiresIn?)
Crea un token especial para operaciones sin credenciales completas.
const token = jwtManager.createCleanCredentialsToken(Operatives.CATALOG, '2h');✅ Métodos de Validación
verify(token, secretKey?, options?)
Valida un JWT y retorna el payload si es válido.
const payload = jwtManager.verify(token);
if (payload) {
console.log('✅ Token válido');
console.log('Usuario:', payload.sub);
console.log('Email:', payload.email);
} else {
console.log('❌ Token inválido o expirado');
}
// Con secret key personalizada
const payload2 = jwtManager.verify(token, 'custom-secret');verifyDetailed(token, secretKey?, options?)
Valida un JWT y retorna resultado detallado con información de error.
const result = jwtManager.verifyDetailed(token);
console.log('Válido:', result.valid);
if (result.valid) {
console.log('Payload:', result.payload);
} else {
console.log('Error:', result.error);
console.log('Tipo de error:', result.errorType);
// errorType: 'expired' | 'invalid' | 'not_before' | 'malformed' | 'signature_invalid'
}🔍 Métodos de Decodificación y Consulta
decode(token, complete?)
Decodifica un JWT sin validarlo (no verifica firma ni expiración).
// Solo payload
const payload = jwtManager.decode(token);
// Con header incluido
const full = jwtManager.decode(token, true);
console.log('Header:', full.header); // { alg: 'HS256', typ: 'JWT' }
console.log('Payload:', full.payload);getClaims(token)
Extrae solo los claims estándar OIDC de un token.
const claims = jwtManager.getClaims(token);
// Retorna solo: sub, name, email, email_verified, picture, etc.
// NO incluye: iss, aud, iat, exp (que son JWT claims)hasScope(token, requiredScope)
Verifica si un token tiene un scope/permiso específico.
const canRead = jwtManager.hasScope(token, 'read:products');
const canDelete = jwtManager.hasScope(token, 'delete:products');
if (canRead) {
console.log('✅ Usuario puede leer productos');
}getJwtKey()
Obtiene la clave secreta configurada.
const secretKey = jwtManager.getJwtKey();🔄 Métodos Legacy (Deprecated)
Estos métodos siguen funcionando para mantener compatibilidad hacia atrás:
// ⚠️ Deprecated - Usa createToken() en su lugar
jwtManager.getTokenWithoutKey(payload, expiresIn, options);
// ⚠️ Deprecated - Usa createTokenWithSecret() en su lugar
jwtManager.getTokenWithKey(payload, secretKey, expiresIn, options);
// ⚠️ Deprecated - Usa createCleanCredentialsToken() en su lugar
jwtManager.getTokenCleanCredentials(operative, expiresIn);
// ⚠️ Deprecated - Usa verify() en su lugar
jwtManager.validateToken(token, secretKey, options);
// ⚠️ Deprecated - Usa verifyDetailed() en su lugar
jwtManager.validateTokenDetailed(token, secretKey, options);
// ⚠️ Deprecated - Usa getClaims() en su lugar
jwtManager.getStandardClaims(token);🔧 Configuración
Variables de entorno
# Clave secreta para firmar tokens (REQUERIDO en producción)
JWT_SECRET=tu_clave_super_secreta_aqui
# Emisor de los tokens (quien genera el token)
JWT_ISSUER=hemia-app
# Audiencia de los tokens (para quién es el token)
JWT_AUDIENCE=hemia-api⚠️ Importante: Nunca uses una clave por defecto en producción. Define
JWT_SECRETsiempre en entornos seguros.
💡 Ejemplos de Uso
Ejemplo 1: Autenticación completa
import { JwtManager } from '@hemia/jwt-manager';
const jwtManager = new JwtManager();
// 1. Usuario inicia sesión
const idToken = jwtManager.createIdToken({
sub: 'user_12345',
name: 'Cristian Mendez',
email: '[email protected]',
email_verified: true
}, '1h');
const accessToken = jwtManager.createAccessToken(
'user_12345',
['read:profile', 'read:products', 'write:orders'],
'15m'
);
const refreshToken = jwtManager.createRefreshToken('user_12345', '30d');
// 2. Enviar tokens al cliente
res.json({ idToken, accessToken, refreshToken });
// 3. Cliente usa accessToken en requests
// Authorization: Bearer <accessToken>
// 4. Validar token en cada request
const payload = jwtManager.verify(accessToken);
if (!payload) {
return res.status(401).json({ error: 'Token inválido' });
}
// 5. Verificar permisos específicos
if (!jwtManager.hasScope(accessToken, 'write:orders')) {
return res.status(403).json({ error: 'Sin permisos' });
}Ejemplo 2: Middleware de autenticación (Express)
import { Request, Response, NextFunction } from 'express';
import { JwtManager } from '@hemia/jwt-manager';
const jwtManager = new JwtManager();
// Middleware de autenticación
export const authenticateToken = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Token no proporcionado' });
}
const payload = jwtManager.verify(token);
if (!payload) {
return res.status(403).json({ error: 'Token inválido o expirado' });
}
req.user = payload;
next();
};
// Middleware de autorización por scope
export const requireScope = (requiredScope: string) => {
return (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token || !jwtManager.hasScope(token, requiredScope)) {
return res.status(403).json({
error: `Permiso denegado. Se requiere: ${requiredScope}`
});
}
next();
};
};
// Uso en rutas
app.get('/profile', authenticateToken, (req, res) => {
res.json({ user: req.user });
});
app.get('/products', authenticateToken, requireScope('read:products'), (req, res) => {
res.json({ products: [...] });
});
app.post('/orders', authenticateToken, requireScope('write:orders'), (req, res) => {
res.json({ success: true });
});Ejemplo 3: Manejo de errores detallado
const result = jwtManager.verifyDetailed(token);
if (!result.valid) {
switch (result.errorType) {
case 'expired':
// Token expirado - solicitar refresh
return res.status(401).json({
error: 'Token expirado',
code: 'TOKEN_EXPIRED',
message: 'Por favor renueva tu sesión'
});
case 'signature_invalid':
// Firma inválida - posible manipulación
console.error('⚠️ Token con firma inválida detectado');
return res.status(403).json({
error: 'Token inválido',
code: 'INVALID_SIGNATURE'
});
case 'not_before':
// Token aún no válido
return res.status(403).json({
error: 'Token no válido aún',
code: 'TOKEN_NOT_YET_VALID'
});
default:
return res.status(403).json({
error: 'Token inválido',
code: 'INVALID_TOKEN'
});
}
}
// Token válido
console.log('Usuario autenticado:', result.payload.sub);Ejemplo 4: Renovar tokens con Refresh Token
app.post('/auth/refresh', async (req, res) => {
const { refreshToken } = req.body;
// Validar refresh token
const payload = jwtManager.verify(refreshToken);
if (!payload || payload.type !== 'refresh') {
return res.status(401).json({ error: 'Refresh token inválido' });
}
// Opcional: Verificar que el jti no esté en lista negra
// const isRevoked = await checkIfRevoked(payload.jti);
// if (isRevoked) return res.status(401).json({ error: 'Token revocado' });
// Generar nuevos tokens
const newAccessToken = jwtManager.createAccessToken(
payload.sub,
['read:profile', 'read:products'],
'15m'
);
const newRefreshToken = jwtManager.createRefreshToken(payload.sub, '30d');
res.json({
accessToken: newAccessToken,
refreshToken: newRefreshToken
});
});📚 TypeScript Types
import {
JwtManager,
StandardClaims,
TokenPayload,
TokenValidationResult,
TokenType
} from '@hemia/jwt-manager';
// StandardClaims (OIDC)
interface StandardClaims {
sub: string; // Subject (required)
name?: string;
given_name?: string;
family_name?: string;
email?: string;
email_verified?: boolean;
picture?: string;
// ... más claims
}
// TokenValidationResult
interface TokenValidationResult {
valid: boolean;
payload?: any;
error?: string;
errorType?: 'expired' | 'invalid' | 'not_before' | 'malformed' | 'signature_invalid';
}
// TokenType
enum TokenType {
ID_TOKEN = 'id_token',
ACCESS_TOKEN = 'access_token',
REFRESH_TOKEN = 'refresh_token'
}🔒 Mejoras de Seguridad Implementadas
✅ Algoritmo Explícito
Previene ataques de confusión de algoritmo especificando siempre HS256 o RS256.
✅ Validación de Issuer y Audience
Verifica que el token fue emitido por quien dice serlo y para la audiencia correcta.
✅ Clock Skew Tolerance
30 segundos de tolerancia para diferencias de reloj entre servidores.
✅ JWT ID (jti) para Refresh Tokens
Permite implementar revocación de tokens mediante lista negra.
✅ Lista Blanca de Algoritmos
Solo acepta HS256 y RS256, rechazando algoritmos inseguros como none.
📖 Documentación Adicional
Para más ejemplos y documentación detallada, consulta USAGE_EXAMPLES.md.
🌐 Recursos
📄 Licencia
MIT — Desarrollado por Hemia Technologies
