Contenido del curso

Fundamentos y Primer CRUD

Base de Datos y Persistencia con TypeORM

Autenticación local con Passport en NestJS

Resumen

La autenticación local con Passport en NestJS te permite proteger endpoints, validar credenciales contra tu base de datos y restringir funcionalidades a usuarios logueados. Si ya tienes el password hasheado y excluido de las respuestas, el siguiente paso lógico es montar el flujo de login con email y contraseña.

¿Qué es Passport y por qué usarlo en NestJS?

Passport es una de las librerías más populares del ecosistema Node para gestionar autenticación, y NestJS ofrece un paquete oficial que la integra de forma natural.

Con Passport puedes manejar múltiples strategies: login local con usuario y contraseña, JWT para sesiones, o autenticación con redes sociales como Facebook, Google o Twitter. La idea es que cada estrategia encapsula una forma distinta de validar identidad, y puedes combinarlas dentro de la misma aplicación.

En este flujo vas a trabajar con la estrategia local, que es la base de cualquier sistema de login tradicional con email y password [2:00].

¿Qué es una strategy en Passport? Es un módulo que define cómo validar credenciales para un método específico de autenticación, por ejemplo local, JWT o OAuth con redes sociales.

¿Qué paquetes necesitas instalar?

Para arrancar la integración necesitas tres dependencias clave en tu proyecto:

  • @nestjs/passport, el wrapper oficial de NestJS.
  • passport y passport-local, la librería base y la estrategia local.
  • @types/passport-local, los tipos para TypeScript.

¿Cómo estructurar el módulo de autenticación?

La recomendación oficial es crear un módulo dedicado para auth, separado del módulo de usuarios. Lo generas con nest generate module auth y luego creas su servicio correspondiente.

Dentro de UsersService necesitas un método nuevo: getUserByEmail. Este método recibe un email como string, ejecuta un findOne en el repositorio y retorna el usuario asociado. Las validaciones de existencia no van aquí, sino en el módulo de autenticación, para lanzar las excepciones correctas en el lugar correcto [5:30].

typescript async getUserByEmail(email: string) { return this.userRepository.findOne({ where: { email } }); }

¿Cómo compartir servicios entre módulos en NestJS?

Aquí entra un concepto fundamental: la cooperación entre módulos. Para que AuthService pueda usar UsersService, necesitas dos pasos.

  1. Exponer UsersService en el array exports del UsersModule.
  2. Importar UsersModule (no el servicio) en el array imports del AuthModule.

Un error común es poner el módulo importado dentro de providers. Eso rompe la aplicación con un mensaje del tipo user service is provider. Los providers son servicios; los módulos van en imports. Esa distinción es la que permite que NestJS resuelva correctamente la inyección de dependencias.

¿Cómo implementar el método validateUser?

El corazón de la estrategia local es un método llamado validateUser dentro de AuthService. Recibe el email y el password en bruto, y devuelve el usuario solo si todo coincide.

El flujo tiene tres momentos:

  • Buscar el usuario con getUserByEmail.
  • Si no existe, lanzar una excepción UnauthorizedException.
  • Comparar el password recibido contra el hash almacenado usando bcrypt.compare.

typescript async validateUser(email: string, password: string) { const user = await this.usersService.getUserByEmail(email); if (!user) throw new UnauthorizedException(); const isMatch = await bcrypt.compare(password, user.password); if (!isMatch) throw new UnauthorizedException(); return user; }

Un detalle de seguridad importante: nunca respondas con user not found. Si lo haces, le confirmas al atacante qué emails están registrados, lo que facilita ataques de fuerza bruta. Devuelve siempre unauthorized sin distinguir entre email inexistente o password incorrecto [9:15].

¿Por qué no decir si el email existe? Porque revelar esa información permite enumerar cuentas registradas. Un mensaje genérico de unauthorized protege la privacidad de tus usuarios.

¿Por qué no necesitas excluir el password manualmente?

La documentación oficial sugiere extraer el password del objeto antes de retornarlo. Si ya configuraste el select: false en la entidad o usas un transformer en el repositorio, ese paso es innecesario. El password queda excluido automáticamente de la respuesta, lo que mantiene tu código más limpio.

¿Cómo crear la LocalStrategy personalizada?

Passport por defecto espera un campo llamado username. Si tu sistema usa email, necesitas configurarlo explícitamente en una clase que extienda de PassportStrategy.

La convención es crear una carpeta strategies/ dentro de auth/ y añadir ahí local.strategy.ts. Esto te permite agregar más estrategias en el futuro (JWT, Google, Twitter) manteniendo el orden.

typescript @Injectable() export class LocalStrategy extends PassportStrategy(Strategy, 'local') { constructor(private authService: AuthService) { super({ usernameField: 'email', passwordField: 'password' }); }

async validate(email: string, password: string) { return this.authService.validateUser(email, password); } }

El segundo parámetro 'local' es el nombre de la estrategia. Es buena práctica nombrarla porque te servirá para referenciarla desde guards más adelante.

¿Qué configuración falta en AuthModule?

Una vez creada la estrategia, queda registrar todo en AuthModule:

  • Importar PassportModule desde @nestjs/passport en el array imports.
  • Importar UsersModule para acceder a UsersService.
  • Registrar LocalStrategy como un provider más, junto a AuthService.

Con esa configuración, el módulo queda listo para validar credenciales contra la base de datos. El siguiente paso natural es exponer un endpoint en un controller que reciba email y password, dispare esta cadena de validación y devuelva la respuesta de login.

¿Ya tienes claro cómo conectarías una segunda estrategia, como JWT, sobre esta misma base? Cuéntame en los comentarios cómo estás organizando tus módulos de auth.