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.
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 (
Requestde 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 HTTP403 Forbidden. - Si el método arroja una excepción explícita (como
UnauthorizedException), NestJS devuelve el código de estado correspondiente (HTTP401).
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.
¿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:
- El paquete oficial
@nestjs/passportexpone la clase basePassportStrategy. - Creamos clases de estrategia dedicadas (como
JwtStrategy) donde configuramos cómo se extrae el token (por ejemplo, desde la cabeceraAuthorization: Bearer <token>) y cómo se valida. - 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 de un Token JWT
Un token JWT consta de tres cadenas de texto separadas por puntos (.):
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).
{
"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,roleo la lista de permisospermissions.
- Información no confidencial útil para la aplicación, como
{
"sub": 12,
"email": "ana@icesi.edu.co",
"permissions": ["users:read", "users:create"],
"iat": 1773517905,
"exp": 1773521505
}
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:
| Criterio | Sesiones Tradicionales (Stateful) | Tokens JWT (Stateless) |
|---|---|---|
| Almacenamiento de Estado | En memoria del servidor o en un almacén centralizado (Redis, DB). | En el cliente (almacenamiento local o cookies HttpOnly). |
| Escalabilidad Horizontal | Requiere 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 Servidor | Crece 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 Inmediata | Inmediata (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:
npm install @nestjs/passport passport passport-jwt @nestjs/jwt
npm install -D @types/passport-jwt
Función de cada paquete:
@nestjs/passport: Módulo oficial que adapta Passport al contenedor de Inyección de Dependencias de NestJS y proveePassportStrategyyAuthGuard.passport: Núcleo del middleware de autenticación para Node.js.passport-jwt: Estrategia de Passport para extraer y verificar tokens JWT desde cabeceras HTTP (Bearer <token>).@nestjs/jwt: Módulo utilitario de NestJS que encapsula la libreríajsonwebtokenpara firmar (sign) y verificar (verify) tokens de manera reactiva e inyectable.@types/passport-jwt: Definiciones tipadas de TypeScript para autocompletado y validación de tipos estáticos en estrategias JWT.