npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@theoptimalpartner/jwt-auth-validator

v2.1.0

Published

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

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 incluyendo client secret
  • Cognito Client Secret para configuraciones seguras
  • Enriquecimiento de datos de usuario con información contextual desde Redis
  • Integración completa con auth-service (permisos, organizaciones, aplicaciones)
  • Logging inteligente - mensajes limpios en producción, debug detallado en desarrollo
  • Manejo de errores mejorado - mensajes user-friendly en lugar de stack traces técnicos
  • 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 @theoptimalpartner/jwt-auth-validator ioredis

Nota: ioredis es requerido ya que el paquete siempre verifica la lista negra de tokens revocados para máxima seguridad.

🚀 Nueva API Simplificada (v2.0+)

✨ Un Solo Método Principal - validate()

import { createCognitoValidator } from "@theoptimalpartner/jwt-auth-validator";

// Configuración con conexión Redis para blacklist y datos de usuario
const validator = createCognitoValidator(
  "us-east-1",
  "us-east-1_XXXXXXXXX",
  "your-client-id",
  "your-client-secret",
  {
    // Configuración Redis para blacklist de tokens y enriquecimiento de datos
    host: process.env.REDIS_HOST || "your-redis-host.com",
    port: parseInt(process.env.REDIS_PORT || "6379"),
    password: process.env.REDIS_PASSWORD,
    tls: process.env.REDIS_TLS === 'true',
    // Opción SSM - Certificado desde AWS Parameter Store (compatible con auth-service)
    caCertPath: process.env.REDIS_CA_CERT_PATH,  // ej: "redis"
    caCertName: process.env.REDIS_CA_CERT_NAME   // ej: "ca-cert"
  },
  true,  // enableApiKeyValidation (opcional)
  true   // enableUserDataRetrieval (opcional)
);

// Inicializar conexión Redis
await validator.initialize();

// 🌟 NUEVO: Un método principal que hace todo
const result = await validator.validate(token, {
  apiKey: 'your-api-key',          // Opcional - validación de API key
  forceSecure: true,               // Opcional - forzar JWKS en desarrollo
  enrichUserData: true,            // Opcional - incluir datos del usuario
  requireAppAccess: false          // Opcional - requiere acceso a aplicación
});

if (result.valid) {
  console.log("✅ Token válido:");
  console.log("Usuario:", result.decoded?.sub);
  console.log("Permisos:", result.userPermissions);
  console.log("Organizaciones:", result.userOrganizations);
  console.log("Aplicaciones:", result.applications);
} else {
  console.log("❌ Token inválido:", result.error);
}

🎯 Métodos Especializados Simples

// Solo validar JWT token (incluye blacklist check)
const basic = await validator.validateToken(token);

// Con API key (validación automática de appId)
const withApi = await validator.validateWithApiKey(token, apiKey);

// Datos completos del usuario
const enriched = await validator.validateEnriched(token, apiKey);

// Acceso estricto a aplicación (DEBE tener acceso)
const strict = await validator.validateWithAppAccess(token, apiKey);

⚡ Uso Rápido - Casos Comunes

// 1. Validación básica de JWT
const result = await validator.validateToken(token);

// 2. Con API key y datos del usuario
const result = await validator.validateWithApiKey(token, apiKey);

// 3. Todo: JWT + API key + datos completos + verificación de appId
const result = await validator.validate(token, {
  apiKey: 'your-api-key',
  enrichUserData: true,
  requireAppAccess: true
});

📊 Validación con Datos Enriquecidos

Datos Completos del Usuario Automáticamente

Con la nueva API, obtener datos del usuario es súper fácil:

// 🌟 Automático: JWT + API key + datos completos del usuario
const result = await validator.validateEnriched(token, apiKey);

if (result.valid) {
  console.log("✅ Usuario autenticado:", result.decoded?.sub);

  // 📊 Datos enriquecidos incluidos automáticamente:
  console.log("🔑 Permisos:", result.userPermissions);
  console.log("🏢 Organizaciones:", result.userOrganizations);
  console.log("📱 Aplicaciones:", result.applications);

  // 🛡️ Autorización basada en datos del usuario
  const isAdmin = result.userOrganizations?.some(org =>
    org.appId === 'my-app' && org.roles.includes('admin')
  );

  const hasPermission = result.userPermissions?.permissions?.some(p =>
    p.includes('users:manage')
  );
}

Control Granular de Datos

// Solo token (sin datos extras)
const basic = await validator.validateToken(token);

// Con datos del usuario incluidos
const enriched = await validator.validate(token, {
  apiKey: 'your-api-key',
  enrichUserData: true          // 👈 Controla si incluir datos del usuario
});

// Sin datos del usuario (más rápido)
const fast = await validator.validate(token, {
  apiKey: 'your-api-key',
  enrichUserData: false         // 👈 Solo validación básica
});

Acceso Directo a Datos de Usuario

// Obtener permisos de un usuario específico
const userPermissions = await validator.getUserPermissions("user-123");

// Obtener organizaciones del usuario
const userOrganizations = await validator.getUserOrganizations("user-123");

// Obtener aplicaciones a las que tiene acceso
const userApplications = await validator.getUserApplications("user-123");

// Obtener datos completos del usuario
const comprehensiveData = await validator.getComprehensiveUserData("user-123");

Patrones de Clave Redis Compatibles

El paquete es totalmente compatible con los patrones de clave de auth-service:

  • Permisos de usuario: user:permissions:{userId}
  • Aplicaciones: app:{appId}
  • Organizaciones: org:{appId}:{organizationId}
  • Roles de aplicación: app:roles:{appId}:{organizationId}
  • Esquemas de aplicación: app-schemas (clave global)
  • Permisos efectivos: permissions:cache:{userId}:{appId}:{orgId}

Interfaces TypeScript para Datos de Usuario

interface EnrichedValidationResult extends ValidationResult {
  userPermissions?: UserPermissions | null;
  userOrganizations?: UserOrganization[];
  applications?: Application[];
}

interface UserPermissions {
  userId: string;
  permissions: {
    [appId: string]: {
      [organizationId: string]: OrganizationPermissions;
    };
  };
  cacheVersion?: number;
}

interface UserOrganization {
  appId: string;
  organizationId: string;
  roles: string[];
  status: 'active' | 'suspended' | 'revoked';
  effectivePermissions?: string[];
}

interface Application {
  appId: string;
  name: string;
  description?: string;
  isActive: boolean;
  allowedDomains?: string[];
  redirectUrls?: string[];
  schema: AppSchema;
  createdAt: number;
  updatedAt: number;
  metadata?: Record<string, unknown>;
}

Gestión de Cache

// Limpiar cache específico del usuario
validator.clearUserCache("user-123");

// Limpiar todo el cache
validator.clearAllCache();

// Obtener estadísticas del cache
const stats = validator.getUserDataStats();
console.log(`Cache hits: ${stats.cacheHits}, misses: ${stats.cacheMisses}`);

Rendimiento y Fallback

  • Cache local con TTL configurable para reducir consultas Redis
  • Fallback graceful: Si Redis no está disponible, devuelve validación básica
  • Lazy loading: Solo carga datos cuando se solicita enriquecimiento
  • Optimización de memoria: Cache inteligente con limpieza automática

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 con API Key opcional
await validator.validateTokenWithApiKey(token: string, apiKey?: string, forceSecure?: boolean): Promise<ValidationResult>

// NUEVO: Validación con enriquecimiento de datos de usuario
await validator.validateTokenEnriched(token: string, apiKey?: string, forceSecure?: boolean): Promise<EnrichedValidationResult>

// 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 segura (siempre usa JWKS)
await validator.validateTokenSecure(token: string): Promise<ValidationResult>

// Validación básica (sin verificación JWKS)
await validator.validateTokenBasic(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

// Extraer API key del header X-API-Key
validator.extractApiKeyFromHeader(apiKeyHeader: string): string | null

// Extraer API key de headers múltiples (X-API-Key, X-Api-Key, API-Key)
validator.extractApiKeyFromHeaders(headers: Record<string, 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

NUEVO: Métodos de datos de usuario

// Obtener permisos de usuario desde Redis
await validator.getUserPermissions(userId: string): Promise<UserPermissions | null>

// Obtener organizaciones del usuario
await validator.getUserOrganizations(userId: string): Promise<UserOrganization[]>

// Obtener aplicaciones accesibles por el usuario
await validator.getUserApplications(userId: string): Promise<Application[]>

// Obtener datos completos del usuario
await validator.getComprehensiveUserData(userId: string): Promise<{
  permissions: UserPermissions | null;
  organizations: UserOrganization[];
  applications: Application[];
}>

// Gestión de cache de datos de usuario
validator.clearUserCache(userId: string): void
validator.clearAllCache(): void
validator.getUserDataStats(): UserDataStats

// Verificar si el enriquecimiento de datos está habilitado
validator.isUserDataEnabled(): boolean

Funciones Helper

createCognitoValidator

Función de conveniencia para crear un validator configurado para AWS Cognito:

createCognitoValidator(
  region: string,                          // AWS region (ej: "us-east-1")
  userPoolId: string,                      // Cognito User Pool ID
  clientId?: string,                       // Cognito App Client ID (opcional)
  clientSecret?: string,                   // Cognito App Client Secret (opcional)
  redisConfig?: {                          // Configuración Redis (opcional)
    host?: string;
    port?: number;
    password?: string;
    tls?: boolean;                       // Nota: Solo boolean - la función configura TLS internamente
    caCertPath?: string;
    caCertName?: string;
  },
  enableApiKeyValidation?: boolean,        // Habilitar validación de API keys (default: false)
  enableUserDataRetrieval?: boolean       // Habilitar enriquecimiento de datos (default: false)
): JWTValidator

Ejemplos de uso:

// Básico - solo validación JWT
const validator = createCognitoValidator("us-east-1", "us-east-1_XXXXXXXXX");

// Con Redis y todas las funciones habilitadas
const validator = createCognitoValidator(
  "us-east-1",
  "us-east-1_XXXXXXXXX",
  "your-client-id",
  "your-client-secret",
  {
    host: "redis-host.com",
    password: "redis-password",
    tls: true
  },
  true,  // enableApiKeyValidation
  true   // enableUserDataRetrieval
);

// Solo con validación de API keys
const validator = createCognitoValidator(
  "us-east-1",
  "us-east-1_XXXXXXXXX",
  "your-client-id",
  undefined,  // sin client secret
  { host: "redis-host.com" },
  true,   // enableApiKeyValidation
  false   // sin enriquecimiento de datos
);

createCognitoValidatorAsync

Versión asíncrona con soporte para AWS Parameter Store:

await createCognitoValidatorAsync(
  region: string,
  userPoolId: string,
  clientId?: string,
  clientSecret?: string,
  redisConfig?: { /* ... */ },
  enableApiKeyValidation?: boolean,
  enableUserDataRetrieval?: boolean
): Promise<JWTValidator>

🔄 Migración desde v1.x

De Múltiples Métodos → Un Método Principal

// ❌ ANTES (v1.x): Múltiples métodos confusos
await validator.validateToken(token);
await validator.validateTokenWithApiKey(token, apiKey);
await validator.validateTokenEnriched(token, apiKey);
await validator.validateTokenWithAppId(token, apiKey);

// ✅ AHORA (v2.x): Un método principal inteligente
await validator.validate(token, {
  apiKey,
  enrichUserData: true
});

Migración Paso a Paso

// Paso 1: Reemplazar validateTokenWithApiKey
// ANTES:
const result = await validator.validateTokenWithApiKey(token, apiKey);

// AHORA:
const result = await validator.validateWithApiKey(token, apiKey);

// Paso 2: Reemplazar validateTokenEnriched
// ANTES:
const result = await validator.validateTokenEnriched(token, apiKey);

// AHORA:
const result = await validator.validateEnriched(token, apiKey);

// Paso 3: Casos complejos
// ANTES: Múltiples llamadas separadas
const tokenResult = await validator.validateTokenWithApiKey(token, apiKey);
const enrichedResult = await validator.validateTokenEnriched(token, apiKey);

// AHORA: Una sola llamada
const result = await validator.validate(token, {
  apiKey,
  enrichUserData: true,
  requireAppAccess: true
});

Compatibilidad hacia Atrás

// ✅ Los métodos antiguos siguen funcionando (con warnings)
const result = await validator.validateTokenWithApiKey(token, apiKey);
// WARNING: validateTokenWithApiKey is deprecated. Use validateWithApiKey() instead.

// Pero es mejor migrar a la nueva API:
const result = await validator.validateWithApiKey(token, apiKey);

Métodos de Client Secret (Cognito)

// Verificar si hay client secret configurado
validator.hasClientSecret(): boolean

// Obtener client secret (si está configurado)
validator.getClientSecret(): string | undefined

// Calcular hash secreto para operaciones Cognito
validator.calculateSecretHash(identifier: string): string

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();

// NUEVO: Funciones de diagnóstico para debugging
const diagnosis = validator.diagnoseToken(token);
console.log("Token diagnosis:", diagnosis);

Configuración Manual

Para casos de uso avanzados, puedes configurar manualmente con control completo sobre TLS:

import { JWTValidator } from "@theoptimalpartner/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",
    clientSecret: "your-client-secret", // Opcional, para mayor seguridad
    cacheTimeout: 3600, // Cache de claves por 1 hora
  },
  enableRedisBlacklist: true,          // Lista negra de tokens revocados
  enableApiKeyValidation: true,        // Validación de API keys
  enableUserDataRetrieval: true,       // Enriquecimiento de datos de usuario
  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));
    },
  },
});

Opciones de Configuración Boolean

El ValidatorConfig soporta las siguientes opciones boolean para habilitar funcionalidades específicas:

  • enableRedisBlacklist?: boolean - Habilita la verificación de lista negra de tokens usando Redis (default: false)
  • enableApiKeyValidation?: boolean - Habilita la validación de API keys para control de acceso a nivel de sistema y aplicación (default: false)
  • enableUserDataRetrieval?: boolean - Habilita el enriquecimiento de datos de usuario con permisos, organizaciones y aplicaciones (default: false)
  • forceSecureValidation?: boolean - Fuerza la validación JWKS segura incluso en entornos de desarrollo (default: false)

Estas banderas boolean proveen control granular sobre qué características están activas, permitiendo optimizar el rendimiento habilitando solo la funcionalidad necesaria.

Ejemplos de Uso

Express.js Middleware

import express from "express";
import { createCognitoValidator } from "@theoptimalpartner/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 "@theoptimalpartner/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;
  }
}

NUEVO: Express.js Middleware con Enriquecimiento de Datos

import express from "express";
import { createCognitoValidator } from "@theoptimalpartner/jwt-auth-validator";

const app = express();

// Validator con configuración de datos de usuario
const validator = createCognitoValidator(
  process.env.AWS_REGION!,
  process.env.COGNITO_USER_POOL_ID!,
  process.env.COGNITO_CLIENT_ID,
  process.env.COGNITO_CLIENT_SECRET,
  {
    host: process.env.REDIS_HOST!,
    password: process.env.REDIS_PASSWORD,
    tls: process.env.REDIS_TLS === 'true',
  },
  {
    enableUserDataRetrieval: true,
    includeApplications: true,
    includeOrganizations: true,
    includeRoles: true,
    cacheTimeout: 300,
  }
);

// Middleware de autenticación con datos de usuario
const enrichedAuthMiddleware = 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" });
    }

    // Usa validateTokenEnriched para obtener datos de usuario
    const result = await validator.validateTokenEnriched(token);

    if (!result.valid) {
      return res.status(401).json({ error: result.error });
    }

    // Contexto enriquecido disponible
    req.user = result.decoded;
    req.userPermissions = result.userPermissions;
    req.userOrganizations = result.userOrganizations;
    req.userApplications = result.applications;

    next();
  } catch (error) {
    res.status(500).json({ error: "Authentication error" });
  }
};

// Middleware de autorización por rol
const requireRole = (appId: string, role: string) => {
  return (req: any, res: any, next: any) => {
    const hasRole = req.userOrganizations?.some((org: any) => 
      org.appId === appId && org.roles.includes(role)
    );

    if (!hasRole) {
      return res.status(403).json({ error: "Insufficient permissions" });
    }

    next();
  };
};

// Uso del middleware
app.use("/api/protected", enrichedAuthMiddleware);
app.use("/api/admin", requireRole("my-app", "admin"));

app.get("/api/protected/profile", (req: any, res: any) => {
  res.json({
    user: req.user,
    organizations: req.userOrganizations,
    applications: req.userApplications,
  });
});

app.get("/api/admin/dashboard", (req: any, res: any) => {
  res.json({ message: "Welcome admin!", user: req.user });
});

Lambda Authorizer

import { createCognitoValidator } from "@theoptimalpartner/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");
  }
};

Validación con API Keys

import express from "express";
import { JWTValidator } from "@theoptimalpartner/jwt-auth-validator";

// Validator con validación de API Keys habilitada
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",
  },
  redis: {
    host: process.env.REDIS_HOST!,
    password: process.env.REDIS_PASSWORD,
    tls: process.env.REDIS_TLS === 'true',
  },
  enableApiKeyValidation: true, // Habilitar validación de API Keys
  enableRedisBlacklist: true,
});

const app = express();

// Middleware que valida JWT con API Key opcional
const authWithApiKeyMiddleware = 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" });
    }

    // Extraer API key de headers
    const apiKey = validator.extractApiKeyFromHeaders(req.headers);
    
    // Validar token con API key opcional
    const result = await validator.validateTokenWithApiKey(token, apiKey);
    
    if (!result.valid) {
      return res.status(401).json({ error: result.error });
    }

    req.user = result.decoded;
    req.apiKey = result.apiKey; // Información del API key si se usó
    
    next();
  } catch (error) {
    res.status(500).json({ error: "Authentication error" });
  }
};

app.use("/api/protected", authWithApiKeyMiddleware);

app.get("/api/protected/data", (req: any, res: any) => {
  res.json({
    user: req.user,
    apiKeyUsed: !!req.apiKey,
    apiKeyInfo: req.apiKey ? {
      name: req.apiKey.name,
      scope: req.apiKey.scope,
      permissions: req.apiKey.permissions
    } : null
  });
});

Solución de Problemas (Troubleshooting)

Problema: "aud is undefined" o errores de audience

Si estás viendo errores relacionados con audience undefined, usa la función de diagnóstico:

import { createCognitoValidator } from "@theoptimalpartner/jwt-auth-validator";

const validator = createCognitoValidator(
  "us-east-1",
  "us-east-1_XXXXXXXXX",
  "tu-client-id", // ⚠️ Asegúrate de pasar el client ID
  "tu-client-secret" // Opcional
);

// Diagnosticar problemas de configuración
const diagnosis = validator.diagnoseToken(yourToken);
console.log("Diagnóstico:", diagnosis);

// Esto te mostrará:
// - Configuración actual (issuer, audience, client secret)
// - Payload del token (aud, client_id, sub, iss, token_use)
// - Lista de problemas detectados

Causas comunes del error de audience:

  1. Client ID no pasado: Asegúrate de pasar el clientId al crear el validator
  2. Token sin aud ni client_id: Algunos flujos de Cognito no incluyen estos campos
  3. Configuración incorrecta: Verifica que el client ID coincida con tu configuración de Cognito

Problema: Tokens que no validan correctamente

// Revisar la configuración y payload del token
const diagnosis = validator.diagnoseToken(token);

if (diagnosis.issues.length > 0) {
  console.error("Problemas detectados:", diagnosis.issues);
  // Ejemplo: ["No audience configured in JWKS config", "Token missing 'sub' claim"]
}

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
COGNITO_CLIENT_SECRET=your-client-secret  # Opcional, para configuraciones seguras

# Configuración AWS para Parameter Store (SSM)
# Nota: Si no se configuran, usa la cadena de credenciales estándar de AWS (aws configure, IAM roles, etc.)
AWS_ACCESS_KEY_ID=your-access-key        # Opcional, usa aws configure si no se proporciona
AWS_SECRET_ACCESS_KEY=your-secret-key    # Opcional, usa aws configure si no se proporciona
AWS_SESSION_TOKEN=your-session-token     # Opcional, para credenciales temporales
AWS_SSM_ENDPOINT=https://ssm.us-east-1.amazonaws.com  # Opcional, para VPC endpoints

# Configuración Redis (requerido)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=''
REDIS_TLS=false
REDIS_CA_CERT_PATH=/path/to/certs
REDIS_CA_CERT_NAME=redis-ca.crt

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

# Debugging y Logging (opcional)
JWT_DEBUG=true  # Habilita logging detallado para debugging
# NODE_ENV=development  # También habilita debug logs
# NODE_ENV=production   # Logs limpios, sin información técnica detallada

Configuración AWS para Development

Desarrollo Local

Para desarrollo local, el paquete usa la cadena de credenciales estándar de AWS:

# Opción 1: Configurar perfil por defecto (recomendado para desarrollo)
aws configure
# Configura: access key, secret key, región, formato

# Opción 2: Usar perfil específico
aws configure --profile mi-proyecto
export AWS_PROFILE=mi-proyecto

# Opción 3: Variables de entorno específicas del proyecto
export AWS_REGION=us-east-1
export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=xyz123...

Orden de Prioridad de Credenciales

  1. Variables de entorno (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY)
  2. Archivo de credenciales (~/.aws/credentials)
  3. Perfil AWS (AWS_PROFILE o [default])
  4. IAM roles (en EC2, ECS, Lambda, etc.)

Permisos Necesarios para SSM

Tu usuario/rol AWS necesita permisos para acceder a Parameter Store:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssm:GetParameter",
        "ssm:GetParameters"
      ],
      "Resource": "arn:aws:ssm:us-east-1:*:parameter/redis/*"
    }
  ]
}

Debugging de Configuración AWS

El paquete incluye logging detallado para diagnosis:

📡 Getting certificate from Parameter Store: /redis/ca-cert
🌍 AWS Region: us-east-1
🔑 Credentials configured: No (using IAM role/profile)  👈 Indica uso de aws configure
✅ Certificate obtained from SSM and cached

AWS Cognito Client Secret

¿Qué es el Client Secret?

El Client Secret es una configuración adicional de seguridad en AWS Cognito que requiere que todas las operaciones incluyan un hash calculado usando HMAC-SHA256. Esto añade una capa extra de seguridad a tu aplicación.

Cuándo usar Client Secret

  • Aplicaciones del lado del servidor: Donde puedes mantener el secret seguro
  • Microservicios: Para validación entre servicios
  • Entornos altamente seguros: Donde se requiere autenticación adicional

Configuración con Client Secret

import { createCognitoValidator } from "@theoptimalpartner/jwt-auth-validator";

// Método 1: Parámetro directo
const validator = createCognitoValidator(
  "us-east-1",
  "us-east-1_XXXXXXXXX",
  "your-client-id",
  "your-client-secret"
);

// Método 2: Variable de entorno (recomendado)
process.env.COGNITO_CLIENT_SECRET = "your-client-secret";
const validator = createCognitoValidator(
  "us-east-1",
  "us-east-1_XXXXXXXXX", 
  "your-client-id"
);

// Verificar si el client secret está configurado
console.log("Client secret configurado:", validator.hasClientSecret());

// Calcular secret hash para operaciones de Cognito
const secretHash = validator.calculateSecretHash("[email protected]");
console.log("Secret hash:", secretHash);

Funciones utilitarias para Client Secret

import { 
  calculateSecretHash, 
  hasClientSecret, 
  safeCalculateSecretHash 
} from "@theoptimalpartner/jwt-auth-validator";

// Calcular hash manualmente
const hash = calculateSecretHash({
  identifier: "[email protected]",
  clientId: "your-client-id", 
  clientSecret: "your-client-secret"
});

// Verificar si un secret está disponible
const hasSecret = hasClientSecret("your-client-secret");

// Calcular hash de forma segura (maneja errores)
const safeHash = safeCalculateSecretHash(
  "[email protected]",
  "your-client-id",
  "your-client-secret"
);

Notas importantes sobre Client Secret

  • Seguridad: Nunca expongas el client secret en el frontend
  • Compatibilidad: Solo usar cuando tu configuración de Cognito lo requiera
  • Opcional: La librería funciona perfectamente sin client secret
  • Consistencia: Los hashes generados son compatibles con AWS SDK

Sistema de Logging y Manejo de Errores

Logging Inteligente

El paquete incluye un sistema de logging que se adapta automáticamente al entorno:

Producción (Logs Limpios)

# En producción, verás mensajes concisos y claros:
JWKS token validation: Token has expired (TOKEN_EXPIRED)
JWT Validator initialization: Configuration error (INITIALIZATION_FAILED)
Token validation: Invalid audience (AUDIENCE_MISMATCH)

Desarrollo (Debug Detallado)

# En desarrollo o con JWT_DEBUG=true, verás información detallada:
NODE_ENV=development  # O JWT_DEBUG=true

# Logs incluyen:
✅ JWKS Service initialized with remote JWKS set using jose library
🔍 JWKS Configuration: { issuer: "...", audience: "...", hasClientSecret: true }
🔍 Token payload aud/client_id: { aud: "...", client_id: "...", token_use: "access" }
🔍 Verify options: { issuer: "...", audience: "...", clockTolerance: "60s" }
✅ Token verified successfully with remote JWKS using jose

Configuración de Logging

// Control de logging por entorno
process.env.NODE_ENV = 'production';  // Logs limpios
process.env.NODE_ENV = 'development'; // Logs detallados

// O control específico de JWT
process.env.JWT_DEBUG = 'true';  // Fuerza debug logs independiente del NODE_ENV

Manejo de Errores User-Friendly

El sistema convierte errores técnicos en mensajes comprensibles:

// Antes (verboso):
// Error: JWTExpired: jwt expired
//   at verify (/node_modules/jsonwebtoken/verify.js:147:19)
//   at JWKSService.validateTokenWithJWKS (/lib/jwks-service.js:195:23)
//   ... [stack trace completo]

// Ahora (limpio):
"Token has expired"

// Con context para debugging:
"JWKS token validation: Token has expired (TOKEN_EXPIRED)"

Funciones de Error Handling

import { 
  extractErrorDetails, 
  getUserFriendlyErrorMessage, 
  logError,
  JWT_ERROR_MESSAGES 
} from "@theoptimalpartner/jwt-auth-validator";

// Obtener detalles estructurados del error
const errorDetails = extractErrorDetails(error, 'Token validation');
console.log(errorDetails); 
// { message: "Token has expired", code: "TOKEN_EXPIRED", context: "Token validation" }

// Obtener mensaje user-friendly
const message = getUserFriendlyErrorMessage(error);
console.log(message); // "Token has expired"

// Log con formato consistente
logError(error, 'Custom operation'); 
// Output: "Custom operation: Token has expired (TOKEN_EXPIRED)"

📋 Estructura del Token Decodificado (decodedToken)

Después de una validación exitosa, el objeto result.decoded contiene los claims del JWT con la siguiente estructura:

Claims Principales

interface DecodedToken {
  // ============ Claims Obligatorios JWT Standard (RFC 7519) ============

  /** Subject - Identificador único del usuario (UUID) */
  sub: string;                        // Ejemplo: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

  /** Audience - Cliente/aplicación para la cual el token fue emitido */
  aud: string;                        // Ejemplo: "1234567890abcdefghijklmnop"

  /** Issuer - URL del User Pool de Cognito que emitió el token */
  iss: string;                        // Ejemplo: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX"

  /** Expiration Time - Timestamp Unix (segundos) de expiración */
  exp: number;                        // Ejemplo: 1735689600 (equivale a 2025-01-01 00:00:00 UTC)

  /** Issued At - Timestamp Unix (segundos) de emisión */
  iat: number;                        // Ejemplo: 1735603200 (equivale a 2024-12-31 00:00:00 UTC)

  /** Token Use - Tipo de token Cognito */
  token_use: 'access' | 'id';         // 'access' para Access Tokens, 'id' para ID Tokens

  // ============ Claims Opcionales de Usuario ============

  /** Email del usuario (opcional) */
  email?: string;                     // Ejemplo: "[email protected]"

  /** Verificación de email (opcional) */
  email_verified?: boolean;           // true si el email ha sido verificado

  /** Número de teléfono (opcional) */
  phone_number?: string;              // Ejemplo: "+12025551234"

  /** Verificación de teléfono (opcional) */
  phone_number_verified?: boolean;    // true si el teléfono ha sido verificado

  /** Nombre de usuario (opcional) */
  username?: string;                  // Ejemplo: "john.doe"

  // ============ Claims Específicos de AWS Cognito ============

  /** Username en formato Cognito (opcional) */
  'cognito:username'?: string;        // Ejemplo: "john.doe" o "Google_123456789"

  /** Grupos de Cognito a los que pertenece el usuario (opcional) */
  'cognito:groups'?: string[];        // Ejemplo: ["admin", "users"]

  /** Scope OAuth2 - Permisos concedidos al token (solo Access Tokens) */
  scope?: string;                     // Ejemplo: "openid email profile"

  /** Authentication Time - Timestamp Unix de cuando el usuario se autenticó (opcional) */
  auth_time?: number;                 // Ejemplo: 1735603200

  /** JWT ID - Identificador único del token (opcional) */
  jti?: string;                       // Ejemplo: "a1b2c3d4-e5f6-7890-abcd-ef1234567890"

  // ============ Custom Attributes ============

  /** Cualquier atributo custom definido en Cognito */
  [key: string]: unknown;             // Ejemplo: { "custom:tenant_id": "company-123" }
}

Ejemplos Reales de Tokens Decodificados

Access Token (token_use: "access")

{
  sub: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  iss: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX",
  client_id: "1234567890abcdefghijklmnop",
  aud: "1234567890abcdefghijklmnop",
  token_use: "access",
  scope: "openid email profile",
  auth_time: 1735603200,
  exp: 1735689600,
  iat: 1735603200,
  jti: "xyz-789-def-456",
  username: "john.doe",
  "cognito:username": "john.doe",
  "cognito:groups": ["admin", "users"]
}

ID Token (token_use: "id")

{
  sub: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  iss: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX",
  aud: "1234567890abcdefghijklmnop",
  token_use: "id",
  auth_time: 1735603200,
  exp: 1735689600,
  iat: 1735603200,
  email: "[email protected]",
  email_verified: true,
  phone_number: "+12025551234",
  phone_number_verified: true,
  "cognito:username": "john.doe",
  "cognito:groups": ["admin"],
  "custom:department": "engineering",
  "custom:employee_id": "EMP-12345"
}

Uso del Token Decodificado en tu Aplicación

const result = await validator.validateToken(token);

if (result.valid && result.decoded) {
  const decoded = result.decoded;

  // ✅ Identificación del usuario
  console.log('User ID:', decoded.sub);
  console.log('Username:', decoded['cognito:username'] || decoded.username);

  // ✅ Información de contacto
  if (decoded.email) {
    console.log('Email:', decoded.email);
    console.log('Email verificado:', decoded.email_verified);
  }

  // ✅ Autorización basada en grupos
  if (decoded['cognito:groups']?.includes('admin')) {
    console.log('Usuario es administrador');
  }

  // ✅ Validación de tiempo
  const expiresAt = new Date(decoded.exp * 1000);
  console.log('Token expira:', expiresAt.toLocaleString());

  const issuedAt = new Date(decoded.iat * 1000);
  console.log('Token emitido:', issuedAt.toLocaleString());

  // ✅ Atributos custom
  if (decoded['custom:tenant_id']) {
    console.log('Tenant ID:', decoded['custom:tenant_id']);
  }

  // ✅ Scope OAuth2 (solo Access Tokens)
  if (decoded.token_use === 'access' && decoded.scope) {
    const scopes = decoded.scope.split(' ');
    console.log('Permisos OAuth2:', scopes);
  }
}

Diferencias entre Access Token e ID Token

| Campo | Access Token | ID Token | |-------|-------------|----------| | token_use | "access" | "id" | | scope | ✅ Incluido | ❌ No incluido | | client_id | ✅ Incluido | ❌ No incluido | | email | ❌ No incluido | ✅ Incluido | | email_verified | ❌ No incluido | ✅ Incluido | | phone_number | ❌ No incluido | ✅ Incluido | | Custom Attributes | ❌ No incluido | ✅ Incluido | | Uso Principal | Autorización en APIs | Información del usuario |

Notas Importantes

  • Claims opcionales: La disponibilidad de campos como email, phone_number, y custom:* depende de tu configuración de Cognito
  • Token Use: Usa decoded.token_use para determinar el tipo de token y qué campos esperar
  • Timestamps: Los campos exp, iat, y auth_time están en formato Unix timestamp (segundos desde 1970-01-01)
  • Custom Attributes: Los atributos custom de Cognito tienen el prefijo custom: en sus nombres
  • Grupos Cognito: Los grupos se almacenan en el array cognito:groups cuando están configurados

📦 Estructura de la Respuesta de Validación (ValidationResult)

Cuando validas un token, el paquete devuelve un objeto ValidationResult con la siguiente estructura completa:

Estructura Completa de ValidationResult

interface ValidationResult {
  /** Indica si el token es válido */
  valid: boolean;

  /** Token JWT decodificado (solo si valid === true) */
  decoded?: DecodedToken;

  /** Información del API Key usado (solo si se validó con API Key) */
  apiKey?: ApiKeyData;

  /** Mensaje de error (solo si valid === false) */
  error?: string;
}

Ejemplo Real de Respuesta Exitosa (con API Key)

const result = await validator.validateWithApiKey(token, apiKey);

// Resultado completo:
{
  valid: true,
  decoded: {
    sub: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    iss: "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_XXXXXXXXX",
    aud: "1234567890abcdefghijklmnop",
    token_use: "access",
    exp: 1735689600,
    iat: 1735603200,
    username: "john.doe",
    "cognito:username": "john.doe",
    "cognito:groups": ["users"]
  },
  apiKey: {
    name: "production-api-key",
    permissions: ["auth:access", "users:read"],
    appId: "my-application",
    scope: "client",
    createdAt: 1735603200000,
    lastUsed: 1735689600000,
    isActive: true,
    metadata: {
      createdFor: "Integration Team",
      description: "API Key for production integration",
      environment: "production"
    }
  }
}

Estructura de ApiKeyData

Cuando se valida con un API Key, el campo apiKey contiene información detallada sobre la clave:

interface ApiKeyData {
  /** Nombre identificador del API Key */
  name: string;                         // Ejemplo: "production-api-key"

  /** Lista de permisos asignados al API Key */
  permissions: string[];                // Ejemplo: ["auth:access", "users:read"]

  /** ID de la aplicación asociada (opcional para scope 'system') */
  appId?: string;                       // Ejemplo: "my-application"

  /** Alcance del API Key */
  scope: 'app' | 'system' | 'client';  // 'app': app específica, 'system': transversal, 'client': cliente

  /** Timestamp Unix de creación del API Key */
  createdAt: number;                    // Ejemplo: 1735603200000 (milisegundos)

  /** Timestamp Unix del último uso (null si nunca se ha usado) */
  lastUsed: number | null;              // Ejemplo: 1735689600000 (milisegundos)

  /** Estado del API Key */
  isActive: boolean;                    // true: activo, false: desactivado

  /** Metadatos adicionales personalizados */
  metadata?: Record<string, unknown>;   // Ejemplo: { createdFor: "Team", environment: "production" }
}

Diferencias entre Scopes de API Keys

| Scope | Descripción | Restricciones | Uso Típico | |-------|-------------|---------------|------------| | system | Acceso transversal a todas las aplicaciones | Ninguna - acceso completo | Administración, integraciones de sistema | | app | Acceso limitado a una aplicación específica | Solo puede acceder al appId asociado | Integraciones de aplicaciones específicas | | client | Acceso de cliente/frontend | Restricciones según permisos asignados | Aplicaciones frontend, móviles |

Ejemplos de Uso con API Key

1. Validación Básica con API Key

const result = await validator.validateWithApiKey(token, apiKey);

if (result.valid) {
  console.log('✅ Token válido');
  console.log('Usuario:', result.decoded?.username);

  // Información del API Key
  if (result.apiKey) {
    console.log('API Key:', result.apiKey.name);
    console.log('Scope:', result.apiKey.scope);
    console.log('Permisos:', result.apiKey.permissions);
    console.log('App ID:', result.apiKey.appId);
  }
} else {
  console.log('❌ Token inválido:', result.error);
}

2. Autorización basada en API Key Scope

const result = await validator.validateWithApiKey(token, apiKey);

if (result.valid && result.apiKey) {
  const { scope, appId, permissions } = result.apiKey;

  // Verificar si tiene acceso system (transversal)
  if (scope === 'system') {
    console.log('✅ Acceso system - puede acceder a todas las apps');
  }

  // Verificar si tiene acceso a app específica
  if (scope === 'app' && appId === 'my-target-app') {
    console.log('✅ Acceso autorizado a my-target-app');
  }

  // Verificar permisos específicos
  if (permissions.includes('users:write')) {
    console.log('✅ Puede modificar usuarios');
  }
}

3. Usar Metadata del API Key

const result = await validator.validateWithApiKey(token, apiKey);

if (result.valid && result.apiKey?.metadata) {
  const metadata = result.apiKey.metadata;

  // Acceder a información personalizada
  console.log('Creado para:', metadata.createdFor);
  console.log('Descripción:', metadata.description);
  console.log('Ambiente:', metadata.environment);

  // Control de acceso basado en ambiente
  if (metadata.environment === 'production') {
    console.log('⚠️ API Key de producción - logging extra habilitado');
  }
}

4. Tracking de Último Uso

const result = await validator.validateWithApiKey(token, apiKey);

if (result.valid && result.apiKey) {
  const { lastUsed, createdAt } = result.apiKey;

  // Verificar última vez usado
  if (lastUsed) {
    const lastUsedDate = new Date(lastUsed);
    console.log('Última vez usado:', lastUsedDate.toLocaleString());

    // Detectar API Keys inactivos (más de 30 días sin uso)
    const daysSinceLastUse = (Date.now() - lastUsed) / (1000 * 60 * 60 * 24);
    if (daysSinceLastUse > 30) {
      console.log('⚠️ API Key inactivo por más de 30 días');
    }
  } else {
    console.log('ℹ️ API Key nunca usado antes');
  }

  // Antigüedad del API Key
  const createdDate = new Date(createdAt);
  console.log('Creado el:', createdDate.toLocaleString());
}

Respuesta con Datos Enriquecidos (EnrichedValidationResult)

Cuando usas validateEnriched(), obtienes campos adicionales:

interface EnrichedValidationResult extends ValidationResult {
  /** Permisos del usuario desde Redis (opcional) */
  userPermissions?: UserPermissions | null;

  /** Organizaciones del usuario (opcional) */
  userOrganizations?: UserOrganization[];

  /** Aplicaciones accesibles por el usuario (opcional) */
  applications?: Application[];
}

Ejemplo Real de Respuesta Enriquecida

const result = await validator.validateEnriched(token, apiKey);

// Resultado completo con datos enriquecidos:
{
  valid: true,
  decoded: {
    sub: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    username: "john.doe",
    email: "[email protected]",
    // ... otros campos del token
  },
  apiKey: {
    name: "production-api-key",
    permissions: ["auth:access", "users:read"],
    appId: "my-application",
    scope: "client",
    createdAt: 1735603200000,
    lastUsed: 1735689600000,
    isActive: true,
    metadata: {
      environment: "production"
    }
  },
  userPermissions: {
    userId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    permissions: {
      "my-app": {
        "org-123": {
          roles: ["admin", "user"],
          effectivePermissions: ["users:read", "users:write"],
          status: "active"
        }
      }
    }
  },
  userOrganizations: [
    {
      appId: "my-app",
      organizationId: "org-123",
      roles: ["admin"],
      status: "active",
      effectivePermissions: ["users:read", "users:write"]
    }
  ],
  applications: [
    {
      appId: "my-app",
      name: "My Application",
      isActive: true,
      schema: { /* AppSchema */ },
      createdAt: 1735603200000,
      updatedAt: 1735689600000
    }
  ]
}

Manejo de Errores

Cuando la validación falla, obtienes un mensaje de error claro:

const result = await validator.validateToken(token);

if (!result.valid) {
  console.log('❌ Error:', result.error);

  // Ejemplos de errores comunes:
  // - "Token has expired"
  // - "Invalid token signature"
  // - "Invalid audience"
  // - "Token is blacklisted"
  // - "API key is inactive"
  // - "No access to application"
}

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;
}

// NUEVO: Utilidades de manejo de errores
interface ErrorDetails {
  message: string;
  code?: string;
  context?: string;
}

// Funciones de utilidad exportadas
function extractErrorDetails(error: unknown, context?: string): ErrorDetails
function getUserFriendlyErrorMessage(error: unknown): string
function logError(error: unknown, context?: string): void

// Constantes de mensajes de error
const JWT_ERROR_MESSAGES = {
  TOKEN_EXPIRED: 'Token has expired',
  INVALID_TOKEN: 'Invalid token format',
  TOKEN_NOT_ACTIVE: 'Token not active yet',
  INVALID_SIGNATURE: 'Invalid token signature',
  // ... más constantes disponibles
} as const

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

Manejo de errores mejorado

  • Mensajes de error amigables y descriptivos
  • Logging limpio sin información técnica excesiva
  • Traducción inteligente de errores JWT a mensajes claros
  • Códigos de error estructurados para manejo programático

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.