Saltar al contenido principal

Guards, Passport y JSON Web Tokens (JWT)

Para implementar una capa de seguridad profesional en NestJS, es indispensable comprender cómo se integran tres piezas clave: el sistema nativo de Guards, la librería de autenticación Passport a través del patrón Strategy, y el estándar de tokens JSON Web Tokens (JWT).

En esta guía analizaremos la función arquitectónica de los Guards en el ciclo de vida de NestJS, el desacoplamiento que ofrece el patrón Strategy y la anatomía criptográfica de un token JWT.


1. ¿Qué es un Guard en NestJS?

Un Guard (guardián) es una clase decorada con @Injectable() que implementa la interfaz CanActivate. Su única responsabilidad es determinar si una solicitud entrante tiene autorización para ser procesada por el método manejador (route handler) correspondiente.

Clic para ampliar

Posición en el Ciclo de Vida

A diferencia de los Middlewares tradicionales de Express (que desconocen qué controlador o método se ejecutará a continuación), los Guards se ejecutan después de los middlewares pero antes de los interceptores y pipes.

Tienen acceso al objeto ExecutionContext, lo que les permite inspeccionar:

  • El contexto de transporte (HTTP, WebSockets, Microservicios o GraphQL).
  • El objeto de solicitud subyacente (Request de Express o Fastify).
  • Los metadatos de la clase controladora y del método específico que va a ser ejecutado (mediante Reflector).

La Interfaz CanActivate

Todo Guard debe declarar el método canActivate:

export interface CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean>;
}
  • Si el método retorna true, la solicitud continúa su camino hacia los pipes e interceptores.
  • Si retorna false, NestJS aborta la solicitud y devuelve automáticamente una respuesta HTTP 403 Forbidden.
  • Si el método arroja una excepción explícita (como UnauthorizedException), NestJS devuelve el código de estado correspondiente (HTTP 401).

2. El Ecosistema Passport y el Patrón Strategy

Passport es el middleware de autenticación más popular y probado en el ecosistema Node.js. Su éxito radica en su diseño modular basado en el patrón de diseño Strategy (Estrategia).

¿Qué es el Patrón Strategy?

El patrón Strategy es un patrón de diseño del comportamiento que permite definir una familia de algoritmos, encapsular cada uno de ellos en una clase independiente y hacer que sus objetos sean intercambiables en tiempo de ejecución.

Clic para ampliar

¿Cómo aplica NestJS el Patrón Strategy con Passport?

En lugar de mezclar la lógica de autenticación en los controladores o middlewares:

  1. El paquete oficial @nestjs/passport expone la clase base PassportStrategy.
  2. Creamos clases de estrategia dedicadas (como JwtStrategy) donde configuramos cómo se extrae el token (por ejemplo, desde la cabecera Authorization: Bearer <token>) y cómo se valida.
  3. En los controladores, simplemente invocamos el guard genérico AuthGuard('jwt'). Si en el futuro cambiamos de mecanismo de autenticación o añadimos OAuth2, la estructura de los controladores permanece intacta.

3. Estructura y Funcionamiento de JSON Web Tokens (JWT) (RFC 7519)

Un JSON Web Token (JWT) es un estándar abierto definido en el RFC 7519 que establece una forma compacta y autónoma (self-contained) de transmitir información estructurada de forma segura entre distintas partes como un objeto JSON.

Anatomía y ciclo de vida de un JWT

Anatomía de un Token JWT

Un token JWT consta de tres cadenas de texto separadas por puntos (.):

JWT=Header.Payload.Signature\text{JWT} = \text{Header} \,.\, \text{Payload} \,.\, \text{Signature}

Ejemplo de Token JWT Codificado
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEyLCJlbWFpbCI6ImFuYUBpY2VzaS5lZHUuY28ifQ.k7G_X9R7q0LwE58vY6pZaM1o9TuQeWd8xP

1. Header (Encabezado)

Indica los metadatos del token: el tipo (typ: "JWT") y el algoritmo criptográfico utilizado para generar la firma digital (por ejemplo, alg: "HS256" para HMAC con SHA-256 o RS256 para claves pública/privada RSA).

Header Decodificado
{
"alg": "HS256",
"typ": "JWT"
}

2. Payload (Carga Útil o Claims)

Contiene las declaraciones (claims) sobre una entidad (usualmente el usuario autenticado) y datos de metadatos adicionales:

  • Claims Registrados (RFC 7519):
    • sub (Subject): Identificador único del usuario (por ejemplo, el ID primario en la base de datos).
    • iat (Issued At): Marca de tiempo epoch en la que fue emitido el token.
    • exp (Expiration Time): Marca de tiempo epoch a partir de la cual el token deja de ser válido.
  • Claims Públicos / Personalizados:
    • Información no confidencial útil para la aplicación, como email, role o la lista de permisos permissions.
Payload Decodificado
{
"sub": 12,
"email": "ana@icesi.edu.co",
"permissions": ["users:read", "users:create"],
"iat": 1773517905,
"exp": 1773521505
}
Los JWT NO son Cifrados

El Header y el Payload están simplemente codificados en Base64Url, no cifrados. Cualquier persona o proxy que intercepte el token puede decodificarlo y leer su contenido de forma inmediata. Nunca coloques contraseñas, claves privadas, números de tarjetas de crédito ni secretos dentro del Payload de un JWT.

3. Signature (Firma Criptográfica)

La firma garantiza la integridad y la autenticidad del token. Se genera combinando el Header codificado, el Payload codificado y una clave secreta conocida únicamente por el servidor de backend (JWT_SECRET):

HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
JWT_SECRET
)

Si un atacante o cliente malicioso intenta modificar el sub o añadir un rol en el Payload, la firma calculada por el servidor dejará de coincidir con la firma presente en el token, rechazando la petición automáticamente.


4. Stateful vs. Stateless: ¿Por qué JWT?

La elección de JWT frente a sesiones tradicionales basadas en cookies impacta directamente la arquitectura y escalabilidad del servidor:

CriterioSesiones Tradicionales (Stateful)Tokens JWT (Stateless)
Almacenamiento de EstadoEn memoria del servidor o en un almacén centralizado (Redis, DB).En el cliente (almacenamiento local o cookies HttpOnly).
Escalabilidad HorizontalRequiere servidores de sesión compartida (sticky sessions o clúster de Redis).Excelente; cualquier réplica del backend con el mismo JWT_SECRET valida peticiones.
Impacto en Memoria del ServidorCrece proporcionalmente con el número de usuarios conectados simultáneamente.Casi nulo; el servidor no guarda registros de sesiones activas en memoria.
Sobrecarga de Red (Payload Size)Muy baja (una cookie contiene solo un SessionID de pocos bytes).Moderada (el token transporta claims, lo que aumenta el tamaño de los headers HTTP).
Revocación InmediataInmediata (basta con eliminar la sesión en la base de datos o en Redis).Desafiante (el token es válido hasta su fecha de expiración, a menos que se use una lista negra).

5. Instalación del Ecosistema de Dependencias

Para implementar esta arquitectura en NestJS, instala las siguientes dependencias de producción y desarrollo:

Terminal
npm install @nestjs/passport passport passport-jwt @nestjs/jwt
npm install -D @types/passport-jwt

Función de cada paquete:

  1. @nestjs/passport: Módulo oficial que adapta Passport al contenedor de Inyección de Dependencias de NestJS y provee PassportStrategy y AuthGuard.
  2. passport: Núcleo del middleware de autenticación para Node.js.
  3. passport-jwt: Estrategia de Passport para extraer y verificar tokens JWT desde cabeceras HTTP (Bearer <token>).
  4. @nestjs/jwt: Módulo utilitario de NestJS que encapsula la librería jsonwebtoken para firmar (sign) y verificar (verify) tokens de manera reactiva e inyectable.
  5. @types/passport-jwt: Definiciones tipadas de TypeScript para autocompletado y validación de tipos estáticos en estrategias JWT.

Cuestionario de Autoevaluación

Cargando cuestionario...