JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 100
  • Score
    100M100P100Q75396F
  • License MIT

JWT token validation package with offline JWKS validation and Redis-based token revocation support

Package Exports

  • @theoptimalpartner/jwt-auth-validator
  • @theoptimalpartner/jwt-auth-validator/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@theoptimalpartner/jwt-auth-validator) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

JWT Auth Validator

Un package de Node.js para validación offline de tokens JWT con soporte para JWKS y lista negra de tokens en Redis. Diseñado especialmente para tokens de AWS Cognito.

Características

  • Validación offline de JWT con verificación de firma usando JWKS
  • Cache inteligente de claves públicas para mejor rendimiento
  • Lista negra de tokens usando Redis para revocación inmediata
  • Soporte completo para AWS Cognito
  • TypeScript con tipado completo
  • Validación flexible (modo desarrollo vs producción)
  • Compatible con Node.js 18+ (última versión LTS)

Instalación

npm install @your-org/jwt-auth-validator

Dependencias opcionales

Si planeas usar la funcionalidad de lista negra de tokens:

npm install ioredis

Uso Rápido

Configuración básica para AWS Cognito

import { createCognitoValidator } from '@your-org/jwt-auth-validator';

// Configuración mínima (solo validación JWKS)
const validator = createCognitoValidator(
  'us-east-1',                    // AWS region
  'us-east-1_XXXXXXXXX',         // User Pool ID
  'your-client-id'               // Client ID (opcional)
);

// Validar un token
const authHeader = 'Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...';
const token = validator.extractTokenFromHeader(authHeader);

if (token) {
  const result = await validator.validateToken(token);
  
  if (result.valid) {
    console.log('Token válido:', result.decoded);
    // result.decoded contiene el payload del token
  } else {
    console.log('Token inválido:', result.error);
  }
}

Configuración avanzada con Redis

import { createCognitoValidator } from '@your-org/jwt-auth-validator';

// Configuración con Redis para lista negra de tokens
const validator = createCognitoValidator(
  'us-east-1',
  'us-east-1_XXXXXXXXX',
  'your-client-id',
  {
    host: 'your-redis-host.com',
    port: 6380,
    password: 'your-redis-password',
    tls: true  // Para conexiones seguras
  }
);

// El validator ahora verificará automáticamente la lista negra
const result = await validator.validateToken(token);

API Completa

JWTValidator

Métodos principales

// Validación principal (automática según ambiente)
await validator.validateToken(token: string, forceSecure?: boolean): Promise<ValidationResult>

// Validación específica para Access Tokens
await validator.validateAccessToken(token: string): Promise<ValidationResult>

// Validación específica para ID Tokens
await validator.validateIdToken(token: string): Promise<ValidationResult>

// Validación múltiple en lote
await validator.validateMultipleTokens(tokens: string[]): Promise<ValidationResult[]>

Utilidades

// Extraer token del header Authorization
validator.extractTokenFromHeader(authHeader: string): string | null

// Verificar si un token está expirado
validator.isTokenExpired(token: string): boolean

// Obtener tiempo restante hasta expiración (en segundos)
validator.getTimeToExpiry(token: string): number

// Decodificar token sin validar
validator.decodeToken(token: string): DecodedToken | null

// Obtener información completa del token
validator.getTokenInfo(token: string): object | null

Gestión de lista negra (requiere Redis)

// Revocar un token específico
await validator.revokeToken(token: string): Promise<void>

// Revocar todos los tokens de un usuario
await validator.revokeUserTokens(userId: string, tokens: string[]): Promise<void>

Estadísticas y monitoreo

// Estadísticas del cache JWKS
validator.getCacheStats()

// Estadísticas de la lista negra
await validator.getBlacklistStats()

// Cerrar conexiones
await validator.disconnect()

Configuración Manual

Para casos de uso avanzados, puedes configurar manualmente:

import { JWTValidator } from '@your-org/jwt-auth-validator';

const validator = new JWTValidator({
  jwks: {
    jwksUri: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX/.well-known/jwks.json',
    issuer: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX',
    audience: 'your-client-id',
    cacheTimeout: 3600  // Cache de claves por 1 hora
  },
  enableRedisBlacklist: true,
  forceSecureValidation: true,  // Siempre usar validación JWKS
  redis: {
    host: 'your-redis-host.com',
    port: 6380,
    password: 'your-password',
    tls: {
      rejectUnauthorized: true,
      checkServerIdentity: () => undefined,
      servername: 'your-redis-host.com',
      minVersion: 'TLSv1.2',
      maxVersion: 'TLSv1.3'
    },
    family: 4,
    connectTimeout: 60000,
    commandTimeout: 30000,
    maxRetriesPerRequest: 3,
    reconnectOnError: (err) => {
      const reconnectErrors = ['READONLY', 'ECONNRESET', 'EPIPE'];
      return reconnectErrors.some(target => err.message.includes(target));
    }
  }
});

Ejemplos de Uso

Express.js Middleware

import express from 'express';
import { createCognitoValidator } from '@your-org/jwt-auth-validator';

const app = express();
const validator = createCognitoValidator('us-east-1', 'us-east-1_XXXXXXXXX');

// Middleware de autenticación
const authMiddleware = async (req: any, res: any, next: any) => {
  try {
    const authHeader = req.headers.authorization;
    const token = validator.extractTokenFromHeader(authHeader);
    
    if (!token) {
      return res.status(401).json({ error: 'Token missing' });
    }
    
    const result = await validator.validateAccessToken(token);
    
    if (!result.valid) {
      return res.status(401).json({ error: result.error });
    }
    
    req.user = result.decoded;
    next();
  } catch (error) {
    res.status(500).json({ error: 'Authentication error' });
  }
};

app.use('/api/protected', authMiddleware);

app.get('/api/protected/profile', (req: any, res: any) => {
  res.json({ user: req.user });
});

Nest.js Guard

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { createCognitoValidator } from '@your-org/jwt-auth-validator';

@Injectable()
export class JwtAuthGuard implements CanActivate {
  private validator = createCognitoValidator(
    process.env.AWS_REGION!,
    process.env.COGNITO_USER_POOL_ID!,
    process.env.COGNITO_CLIENT_ID
  );

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const authHeader = request.headers.authorization;
    
    const token = this.validator.extractTokenFromHeader(authHeader);
    if (!token) return false;
    
    const result = await this.validator.validateToken(token);
    
    if (result.valid) {
      request.user = result.decoded;
      return true;
    }
    
    return false;
  }
}

Lambda Authorizer

import { createCognitoValidator } from '@your-org/jwt-auth-validator';

const validator = createCognitoValidator(
  process.env.AWS_REGION!,
  process.env.COGNITO_USER_POOL_ID!
);

export const handler = async (event: any) => {
  try {
    const token = validator.extractTokenFromHeader(event.authorizationToken);
    
    if (!token) {
      throw new Error('Unauthorized');
    }
    
    const result = await validator.validateToken(token);
    
    if (!result.valid) {
      throw new Error('Unauthorized');
    }
    
    return {
      principalId: result.decoded!.sub,
      policyDocument: {
        Version: '2012-10-17',
        Statement: [{
          Action: 'execute-api:Invoke',
          Effect: 'Allow',
          Resource: event.methodArn
        }]
      },
      context: {
        userId: result.decoded!.sub,
        email: result.decoded!.email
      }
    };
  } catch (error) {
    throw new Error('Unauthorized');
  }
};

Variables de Entorno

# Configuración básica
AWS_REGION=us-east-1
COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
COGNITO_CLIENT_ID=your-client-id

# Configuración Redis (opcional)
REDIS_HOST=your-redis-host.com
REDIS_PORT=6380
REDIS_PASSWORD=your-password
REDIS_TLS=true

# Forzar validación segura
NODE_ENV=production  # Automáticamente usa validación JWKS en producción

Tipos TypeScript

interface DecodedToken {
  sub: string;
  email?: string;
  email_verified?: boolean;
  phone_number?: string;
  phone_number_verified?: boolean;
  aud: string;
  iss: string;
  exp: number;
  iat: number;
  token_use: 'access' | 'id';
  scope?: string;
  auth_time?: number;
  jti?: string;
  username?: string;
  'cognito:username'?: string;
  'cognito:groups'?: string[];
  [key: string]: unknown;
}

interface ValidationResult {
  valid: boolean;
  decoded?: DecodedToken;
  error?: string;
}

Rendimiento

Cache JWKS

  • Las claves públicas se cachean por 1 hora por defecto
  • Reduce significativamente las consultas a los endpoints JWKS
  • Cache configurable por necesidades específicas

Validación offline

  • No requiere llamadas al servicio de autenticación para validar tokens
  • Validación local usando claves públicas cacheadas
  • Ideal para microservicios y alta concurrencia

Lista negra eficiente

  • Usa Redis para almacenamiento distribuido de tokens revocados
  • TTL automático basado en la expiración del token
  • Optimización de memoria usando hashes de tokens

Seguridad

Validación robusta

  • Verificación de firma usando claves públicas
  • Validación de claims estándar (exp, iss, aud)
  • Verificación específica de tokens Cognito

Modo de desarrollo

  • Validación básica sin verificación de firma para desarrollo
  • Advertencias claras cuando no se usa validación segura
  • Automáticamente usa validación segura en producción

Licencia

MIT

Contribuciones

Las contribuciones son bienvenidas. Por favor:

  1. Fork el repositorio
  2. Crea una rama para tu feature (git checkout -b feature/nueva-funcionalidad)
  3. Commit tus cambios (git commit -am 'Agregar nueva funcionalidad')
  4. Push a la rama (git push origin feature/nueva-funcionalidad)
  5. Crea un Pull Request

Soporte

Para reportar bugs o solicitar features, por favor crea un issue en el repositorio.