Nest
Tutorial Nest: Login y registro
Nest login registro usuarios: implementación. Aprende a implementar sistemas de login y registro de usuarios en Nest con ejemplos prácticos.
Aprende Nest y certifícateLos procesos de registro (sign-up) y login (sign-in) son la puerta de entrada a tu aplicación, esenciales para gestionar el acceso de usuarios y proteger tus recursos. En esta lección, integraremos completamente el registro y el login utilizando JSON Web Tokens (JWT), construyendo sobre la base que ya hemos sentado con las lecciones anteriores.
Configuración inicial
Antes de sumergirse en la lógica de autenticación, se necesita una nueva aplicación NestJS e instalar las siguientes dependencias:
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt class-validator class-transformer @nestjs/mapped-types
# o
yarn add @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt class-validator class-transformer @nestjs/mapped-types
Definición del Role
Enum y la Entidad User
Como ya establecimos en lecciones anteriores, tenemos nuestro Role
enum y la entidad User
que representa la tabla de usuarios en nuestra base de datos. Nos aseguraremos de que esta entidad sea la base para nuestro AuthModule
.
src/user/role.enum.ts7
export enum Role {
USER = 'user',
ADMIN = 'admin',
}
src/user/user.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import { Role } from './role.enum'; // Asegúrate de que la ruta sea correcta
@Entity('users')
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password?: string;
@Column({ nullable: true })
phone: string;
@Column({ nullable: true })
addressStreet: string;
@Column({ nullable: true })
photoUrl: string;
@Column({
type: 'enum',
enum: Role,
default: Role.USER,
})
role: Role;
}
DTOs para Registro y Login
Para la validación de la entrada, utilizaremos DTOs. Asumimos que RegisterDto
ya existe, y crearemos o confirmaremos LoginDto
.
src/auth/dto/register.dto.ts
import {
IsEmail,
IsString,
MinLength,
IsNotEmpty,
IsOptional,
} from 'class-validator';
export class RegisterDto {
@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.' })
@MinLength(6, { message: 'La contraseña debe tener al menos 6 caracteres.' })
@IsNotEmpty({ message: 'La contraseña es un campo requerido.' })
password: string;
// Puedes añadir más campos opcionales si son parte de tu formulario de registro inicial
@IsOptional()
@IsString()
phone?: string;
@IsOptional()
@IsString()
addressStreet?: string;
@IsOptional()
@IsString()
photoUrl?: string;
}
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;
}
Definición de la Interfaz para el Payload JWT
Para una mejor tipificación, definiremos la estructura del payload que se codifica y decodifica en el JWT.
src/auth/interfaces/jwt-payload.interface.ts
import { Role } from '../../user/role.enum'; // Asegúrate de que la ruta sea correcta
export interface JwtPayload {
sub: number; // El ID del usuario (subject)
email: string;
role: Role;
}
Configuración del AuthModule
Nuestro AuthModule
ya existente centraliza toda la lógica de autenticación. Nos aseguraremos de que esté correctamente configurado para trabajar con TypeORM, Passport y JWT, incluyendo la lectura segura del secreto JWT desde variables de entorno.
src/auth/auth.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { User } from '../user/user.entity';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { JwtStrategy } from './strategies/jwt.strategy';
@Module({
imports: [
TypeOrmModule.forFeature([User]),
PassportModule,
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (configService: ConfigService) => ({
secret: configService.get<string>('JWT_SECRET'),
signOptions: { expiresIn: '7d' },
}),
inject: [ConfigService],
}),
],
controllers: [AuthController],
providers: [
AuthService,
JwtStrategy,
],
exports: [AuthService, JwtStrategy, PassportModule],
})
export class AuthModule { }
Servicio de Autenticación (AuthService
)
Este servicio, ya existente, contendrá la lógica principal para el registro y el login. Implementaremos el método login
y ajustaremos el register
para la exclusión segura de la contraseña al devolver el usuario.
src/auth/auth.service.ts
import {
Injectable,
ConflictException,
InternalServerErrorException,
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';
import { JwtService } from '@nestjs/jwt';
import { Role } from '../user/role.enum';
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
private jwtService: JwtService,
) {}
/**
* Registra un nuevo usuario en el sistema.
* Cifra la contraseña de forma segura y verifica que el email no esté ya en uso.
*/
async register(registerDto: RegisterDto): Promise<Omit<User, 'password'>> { // Tipo de retorno sin 'password'
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);
// Creamos una copia del objeto usuario sin la contraseña por seguridad
const { password: _, ...result } = newUser; // Desestructura para omitir 'password'
return result; // Devuelve el nuevo objeto sin la contraseña
} catch (error) {
console.error('Error al registrar usuario:', error);
throw new InternalServerErrorException('Error al registrar el usuario. Por favor, inténtelo de nuevo.');
}
}
/**
* Autentica a un usuario y genera un token JWT si las credenciales son correctas.
* Utiliza bcrypt.compare para verificar la contraseña de forma segura.
*/
async login(loginDto: LoginDto): Promise<{ accessToken: string }> {
const { email, password } = loginDto;
const user = await this.userRepository.findOne({ where: { email } });
// Si el usuario no existe O la contraseña no coincide, lanza UnauthorizedException.
// Esto evita la enumeración de usuarios por razones de seguridad.
if (!user || !(await bcrypt.compare(password, user.password ?? ''))) {
throw new UnauthorizedException('Credenciales incorrectas (email o contraseña).');
}
// Preparamos el payload para el token JWT con información esencial del usuario
const payload = {
sub: user.id,
email: user.email,
role: user.role,
};
const accessToken = await this.jwtService.signAsync(payload);
return { accessToken }; // Devuelve el token de acceso
}
}
Controlador de Autenticación (AuthController
)
El AuthController
ya existente, expone los endpoints HTTP para el registro y el login, delegando la lógica al AuthService
.
src/auth/auth.controller.ts
import {
Controller,
Post,
Body,
HttpCode,
HttpStatus,
// Ya no necesitamos UsePipes o ValidationPipe aquí si es global en main.ts
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { RegisterDto } from './dto/register.dto';
import { LoginDto } from './dto/login.dto';
import { User } from '../user/user.entity';
@Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
/**
* Endpoint para registrar nuevos usuarios.
* Utiliza RegisterDto para la validación de entrada.
*/
@Post('register')
@HttpCode(HttpStatus.CREATED)
async register(@Body() registerDto: RegisterDto): Promise<Omit<User, 'password'>> {
return this.authService.register(registerDto);
}
/**
* Endpoint para el login de usuarios.
* Recibe las credenciales en LoginDto y, si son válidas, devuelve un JWT.
*/
@Post('login')
@HttpCode(HttpStatus.OK)
async login(@Body() loginDto: LoginDto): Promise<{ accessToken: string }> {
return this.authService.login(loginDto);
}
}
Estrategia JWT de Passport (JwtStrategy
)
La JwtStrategy
(ya existente) se encarga de validar los tokens JWT entrantes. La hemos ajustado para leer el secreto de forma segura y para devolver el usuario sin la contraseña.
src/auth/strategies/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { InjectRepository } from '@nestjs/typeorm';
import { Strategy, ExtractJwt } from 'passport-jwt';
import { Repository } from 'typeorm';
import { User } from '../../user/user.entity';
import { JwtPayload } from '../interfaces/jwt-payload.interface';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
private configService: ConfigService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: configService.get<string>('JWT_SECRET'), // Lee la clave secreta de .env
});
}
/**
* Método `validate` de Passport.
* Se llama después de que el token JWT ha sido decodificado y verificado.
* `payload` contiene los datos que firmaste en el token (id, email, role).
* Carga el usuario de la DB y lo adjunta a `request.user`.
*/
async validate(payload: JwtPayload): Promise<Omit<User, 'password'>> { // Tipo de retorno sin 'password'
const { sub: id, email } = payload;
const user = await this.userRepository.findOne({ where: { id, email } });
if (!user) {
throw new UnauthorizedException('Token inválido o usuario no encontrado.');
}
// Creamos una copia del objeto usuario sin la contraseña por seguridad.
// Este objeto es el que estará disponible en `request.user` en tus controladores protegidos.
const { password: _, ...result } = user; // Desestructura para omitir 'password'
return result; // Devuelve el objeto usuario sin la contraseña
}
}
Configuración del AppModule
Tu AppModule
se mantiene limpio, ya que AuthModule
centraliza la configuración de TypeORM (para User
), Passport y JWT.
src/app.module.ts
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthModule } from './auth/auth.module'; // Importa tu AuthModule
@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,
}),
inject: [ConfigService],
}),
AuthModule, // Tu módulo de autenticación
// Aquí importarías otros módulos de tu aplicación (ej. BookModule, ProductsModule)
],
})
export class AppModule {}
Probando el Flujo Completo
Ahora tienes un sistema de autenticación completo y seguro.
- Asegúrate de que
JWT_SECRET
esté en tu.env
. - Inicia tu aplicación NestJS. (
npm run start
) - Realiza un Registro:
- Método:
POST
- URL:
http://localhost:3000/auth/register
- Body (raw JSON):
- Método:
{
"email": "user2@gmail.com",
"password": "admin1",
"phone": "987654321"
}
- Realiza un Login:
- Método:
POST
- URL:
http://localhost:3000/auth/login
- Body (raw JSON):
- Método:
{
"email": "user2@gmail.com",
"password": "admin1"
}
- Si las credenciales son válidas, obtendrás un
200 OK
con unaccessToken
. Este es el token que el frontend usará para acceder a rutas protegidas.
Otras lecciones de Nest
Accede a todas las lecciones de Nest y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción E Instalación Nestjs
Introducción Y Entorno
Comandos Nestjs Cli
Introducción Y Entorno
Métodos Get En Controladores
Controladores
Métodos Post En Controladores
Controladores
Métodos Put En Controladores
Controladores
Métodos Delete En Controladores
Controladores
Gestión De Errores En Controladores
Controladores
Recibir Y Servir Imágenes
Controladores
Desarrollo E Inyección De Servicios
Servicios E Inyección De Dependencias
Crear Y Utilizar Módulos
Módulos
Configuración De Typeorm Con Mysql
Typeorm
Creación De Entidades Y Tablas
Typeorm
Decoradores En Entidades
Typeorm
Crud Con Repositorios
Typeorm
Filtrados En Consultas De Repositorios
Typeorm
Registro De Usuarios
Autenticación
Iniciar Sesión Método De Login
Autenticación
Estrategia De Autenticación Jwtstrategy
Autenticación
Login Y Registro
Autenticación
Estrategia De Autenticación Jwt
Autenticación
Ejercicios de programación de Nest
Evalúa tus conocimientos de esta lección Login y registro con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Métodos GET en controladores
Método find en un servicio con repositorio
Desarrollo e inyección de servicios
Creación de entidades y tablas
Filtrados en consultas de repositorios
Método GET en un controlador
Estrategia de autenticación JwtStrategy
Método PUT en un controlador
CRUD con repositorios
Método delete en un servicio con repositorio
Método DELETE en un controlador
API REST CRUD de entidad Restaurante con TypeORM
Creación de una entidad TypeORM
Crear y utilizar módulos
Métodos PUT en controladores
Iniciar sesión método de login
Configuración de TypeORM con MySQL
Gestión de errores en controladores
Decoradores en entidades
Instalación NestJS
Recibir y servir imágenes
Estrategia de autenticación JWT
Comandos NestJS CLI
Método POST en un controlador
Login y registro
API REST CRUD de entidades Product y Manufacturer
Método save en un servicio con repositorio
Registro de usuarios
Métodos POST en controladores
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender los fundamentos de la autenticación.
- Aprender a implementar el registro y el login en NestJS.
- Conocer las estrategias de autenticación más comunes.
- Implementar el registro de usuarios y el login con JWT.