Introducción
Después de haber implementado el registro de usuarios, el siguiente paso crucial es crear un método de login para que los usuarios puedan autenticarse. Este método verificará las credenciales del usuario y, si son correctas, generará un Token Web JSON (JWT), que el frontend podrá usar para acceder a rutas protegidas.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Instalar librerías
Instalar las librerías de autenticación y generación de tokens JWT, además de bcrypt para verificar contraseñas cifradas:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
# o
yarn add @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
Configurar JwtModule
¡Muy Importante! La clave secreta (secret
) para firmar tus tokens JWT debe ser larga, compleja y nunca expuesta en tu código fuente. Es una buena práctica generarla y almacenarla como una variable de entorno.
Puedes usar este script de Node.js para generar una clave segura:
secrets.js
const crypto = require('crypto');
const secret = crypto.randomBytes(32).toString('hex'); // Genera 32 bytes (256 bits) de aleatoriedad
console.log(secret);
Ejecuta este script una sola vez (node secrets.js
), copia la clave generada y guárdala en tu archivo .env
en la raíz de tu proyecto:
.env
# ... otras variables de entorno ...
JWT_SECRET=tu_clave_secreta_segura_generada_aqui
Configuración del Módulo JWT en AppModule
Vamos a configurar JwtModule
en tu AppModule
usando forRootAsync
para que pueda leer la clave secreta desde las variables de entorno de forma segura a través de ConfigService
. También importamos PassportModule
, que es la base para la estrategia JWT.
// src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config'; // Importa ConfigService
import { AuthModule } from './auth/auth.module';
import { User } from './user/user.entity';
import { JwtModule } from '@nestjs/jwt'; // Importa JwtModule
import { PassportModule } from '@nestjs/passport'; // Importa PassportModule
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: '.env',
}),
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
type: configService.get<'mysql'>('DB_TYPE'),
host: configService.get<string>('DB_HOST'),
port: configService.get<number>('DB_PORT'),
username: configService.get<string>('DB_USERNAME'),
password: configService.get<string>('DB_PASSWORD'),
database: configService.get<string>('DB_DATABASE'),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
synchronize: true, // ¡Recuerda que esto nunca en producción! Usar migraciones.
}),
inject: [ConfigService],
}),
// Configuración del módulo JWT para la firma de tokens
JwtModule.forRootAsync({
imports: [ConfigModule], // Importa ConfigModule para acceder a variables de entorno
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'), // Lee la clave secreta de las variables de entorno
signOptions: { expiresIn: '7d' }, // El token expira en 7 días
}),
inject: [ConfigService], // Inyecta ConfigService en la factory
}),
PassportModule, // Necesario para la autenticación basada en estrategias (como JWT)
AuthModule, // Nuestro módulo de autenticación
],
})
export class AppModule {}
Creación del DTO para el Login
Definiremos un DTO simple para recibir las credenciales del usuario al intentar iniciar sesión.
src/auth/dto/login.dto.ts
import { IsEmail, IsString, IsNotEmpty } from 'class-validator';
export class LoginDto {
@IsEmail({}, { message: 'El email debe ser una dirección de correo válida.' })
@IsNotEmpty({ message: 'El email es un campo requerido.' })
email: string;
@IsString({ message: 'La contraseña debe ser una cadena de texto.' })
@IsNotEmpty({ message: 'La contraseña es un campo requerido.' })
password: string;
}
Desarrollo del Servicio AuthService
(Lógica de Login)
Ahora, añadiremos la lógica de login a nuestro AuthService
. Aquí es donde verificaremos las credenciales y generaremos el JWT.
// src/auth/auth.service.ts
import {
Injectable,
ConflictException,
InternalServerErrorException,
UnauthorizedException, // Importa UnauthorizedException
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from '../user/user.entity';
import * as bcrypt from 'bcrypt';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto'; // Importa el LoginDto
import { JwtService } from '@nestjs/jwt'; // Importa JwtService
import { Role } from '../user/role.enum';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService, // Inyecta JwtService
) {}
async register(registerDto: RegisterDto): Promise<User> {
const { email, password, ...userData } = registerDto;
const existingUser = await this.userRepository.findOne({ where: { email } });
if (existingUser) {
throw new ConflictException('El email ya está registrado.');
}
try {
const hashedPassword = await bcrypt.hash(password, 10);
const newUser = this.userRepository.create({
email,
password: hashedPassword,
role: Role.USER,
...userData
});
await this.userRepository.save(newUser);
delete newUser.password; // No devolver la contraseña cifrada
return newUser;
} catch (error) {
console.error('Error al registrar usuario:', error);
throw new InternalServerErrorException('Error al registrar el usuario. Por favor, inténtelo de nuevo.');
}
}
// Nuevo método de login
async login(loginDto: LoginDto): Promise<{ accessToken: string }> { // Retorna un objeto con el token
const { email, password } = loginDto;
// 1. Buscar el usuario por email
const user = await this.userRepository.findOne({ where: { email } });
// 2. Verificar si el usuario existe y si la contraseña es correcta
// Es mejor lanzar UnauthorizedException para ambos casos por seguridad (prevenir enumeración de usuarios)
if (!user || !(await bcrypt.compare(password, user.password))) {
throw new UnauthorizedException('Credenciales incorrectas (email o contraseña).'); // HTTP 401 Unauthorized
}
// 3. Crear el payload del token JWT
const payload = {
sub: user.id, // 'sub' (subject) es una convención para el ID del usuario
email: user.email,
role: user.role,
// Puedes añadir más datos relevantes aquí, pero evita información sensible
};
// 4. Generar y devolver el token de acceso
const accessToken = await this.jwtService.signAsync(payload);
return { accessToken }; // Retorna el token envuelto en un objeto
}
}
Actualización del Controlador AuthController
Ahora, añadimos el endpoint login
al AuthController
, que delegará la autenticación al AuthService
.
// src/auth/auth.controller.ts
import {
Controller,
Post,
Body,
HttpCode,
HttpStatus,
ConflictException,
UsePipes,
ValidationPipe,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto'; // Importa LoginDto
import { User } from '../user/user.entity';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
@Post('register')
@HttpCode(HttpStatus.CREATED)
async register(@Body() registerDto: RegisterDto): Promise<User> {
return this.authService.register(registerDto);
}
// Nuevo método de login
@Post('login')
@HttpCode(HttpStatus.OK) // Retorna un 200 OK si el login es exitoso
async login(@Body() loginDto: LoginDto): Promise<{ accessToken: string }> {
return this.authService.login(loginDto);
}
}
Configuración de main.ts
para Validación Global
Asegúrate de que tu main.ts
tenga configurado el ValidationPipe
globalmente para que los DTOs funcionen correctamente.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
await app.listen(3000);
}
bootstrap();
Recepción del token
Una aplicación frontend debería tener un formulario de login que llame al método login del controlador de NestJS.
Por ejemplo en Angular podría ser login.component.ts.
Ejemplo de petición de login:
Una vez completado el login, el frontend recibe un token JWT, ejemplo de respuesta de login:
Ejemplo de token JWT obtenido:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOjEsImVtYWlsIjoidXNlcjFAZ21haWwuY29tIiwicm9sZSI6ImFk.....
Tambien podemos usar Postman o Insomnia como en lecciones pasadas para comprobar el endpoint de login.
- Ruta:
POST http://localhost:3000/auth/login
- Cuerpo de la Solicitud (JSON):
{
"email": "user1@gmail.com",
"password": "admin1"
}
- Si las credenciales son correctas, recibirás un
200 OK
con un objeto JSON que contendrá tuaccessToken
compuesto tambien por un token JWT. - Si las credenciales son incorrectas (email no registrado o contraseña errónea), recibirás un
401 Unauthorized
. - Si los datos enviados no cumplen con el formato del DTO, recibirás un
400 Bad Request
.
Verificar token JWT
En jwt.io se puede comprobar el token y su estructura interna:
Aprendizajes de esta lección
- Configurar la seguridad JWT en NestJS
- Generar una clave secreta para firmar token JWT
- Generar token JWT en login
- Realizar login de usuarios (autenticar usuarios)
Completa Nest y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs