Skip to content
On this page

Autenticación y Registro con JWT en Nest.js: Implementación de un Sistema de Login y Register

En esta guía, aprenderás cómo implementar un sistema de autenticación robusto y seguro para tu aplicación Nest.js.

Utilizaremos JSON Web Tokens (JWT) para permitir que los usuarios se registren en tu plataforma y accedan a sus cuentas de manera segura. El proceso de login y register es fundamental para cualquier aplicación que requiera autenticación de usuarios. Con JWT, podremos generar tokens de acceso que protegerán nuestras rutas y garantizarán que solo los usuarios autenticados puedan acceder a determinados recursos.

Documentación oficial de Nest.js:

Código Github

Ayúdame a seguir creando contenido 😍

Tienes varias jugosas alternativas:

Muchas gracias por su tremendo apoyo 😊

Dependencias

sh
bcryptjs
@nestjs/jwt

Users

En una aplicación Nest.js, los usuarios desempeñan un papel crucial en la autenticación y autorización. Aquí te mostramos cómo configurar la entidad de usuario y el módulo correspondiente para gestionar la información de los usuarios.

sh
nest g res users --no-spec

Entidad de Usuario

Definiremos una entidad llamada User para representar la información de nuestros usuarios en la base de datos. Esta entidad contendrá campos importantes como id, name, email, password, rol, y deletedAt.

src\users\entities\user.entity.ts

ts
import { Column, DeleteDateColumn, Entity } from "typeorm";

@Entity()
export class User {
  @Column({ primary: true, generated: true })
  id: number;

  @Column({ length: 500 })
  name: string;

  @Column({ unique: true, nullable: false })
  email: string;

  @Column({ nullable: false })
  password: string;

  @Column({ default: "user" })
  rol: string;

  @DeleteDateColumn()
  deletedAt: Date;
}

Módulo de Usuarios

Crearemos un módulo para gestionar los usuarios en nuestra aplicación. Utilizaremos el módulo TypeOrmModule para incluir nuestra entidad User y proporcionar acceso a la base de datos. Además, exportaremos el módulo TypeOrmModule para que podamos utilizarlo en otros módulos de nuestra aplicación.

src\users\users.module.ts

ts
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { User } from "./entities/user.entity";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";

@Module({
  imports: [TypeOrmModule.forFeature([User])],
  controllers: [UsersController],
  providers: [UsersService],
  exports: [UsersService],
})
export class UsersModule {}

src\users\dto\create-user.dto.ts

ts
export class CreateUserDto {
  name: string;
  email: string;
  password: string;
}

src\users\users.service.ts

ts
import { Injectable } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { CreateUserDto } from "./dto/create-user.dto";
import { UpdateUserDto } from "./dto/update-user.dto";
import { User } from "./entities/user.entity";

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private readonly usersRepository: Repository<User>
  ) {}

  async create(createUserDto: CreateUserDto) {
    return await this.usersRepository.save(createUserDto);
  }

  async findOneByEmail(email: string) {
    return await this.usersRepository.findOneBy({ email });
  }
}

Auth

sh
yarn add bcryptjs
sh
nest g module auth
nest g controller auth
nest g service auth

auth module

src\auth\auth.module.ts

ts
import { Module } from "@nestjs/common";
import { UsersModule } from "src/users/users.module";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";

@Module({
  imports: [UsersModule],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

auth dto

src\auth\dto\register.dto.ts

ts
import { Transform } from "class-transformer";
import { IsEmail, IsString, MinLength } from "class-validator";

export class RegisterDto {
  @IsString()
  @MinLength(1)
  name: string;

  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  @Transform(({ value }) => value.trim())
  password: string;
}

src\auth\dto\login.dto.ts

ts
import { Transform } from "class-transformer";
import { IsEmail, IsString, MinLength } from "class-validator";

export class LoginDto {
  @IsEmail()
  email: string;

  @IsString()
  @MinLength(6)
  @Transform(({ value }) => value.trim())
  password: string;
}

auth service

src\auth\auth.service.ts

ts
import {
  BadRequestException,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { RegisterDto } from "./dto/register.dto";

import { JwtService } from "@nestjs/jwt";
import * as bcryptjs from "bcryptjs";
import { UsersService } from "src/users/users.service";
import { LoginDto } from "./dto/login.dto";

@Injectable()
export class AuthService {
  constructor(private readonly usersService: UsersService) {}

  async register({ password, email, name }: RegisterDto) {
    const user = await this.usersService.findOneByEmail(email);

    if (user) {
      throw new BadRequestException("Email already exists");
    }

    const hashedPassword = await bcryptjs.hash(password, 10);

    await this.usersService.create({
      name,
      email,
      password: hashedPassword,
    });

    return {
      message: "User created successfully",
    };
  }

  async login({ email, password }: LoginDto) {
    const user = await this.usersService.findOneByEmail(email);

    if (!user) {
      throw new UnauthorizedException("Invalid email");
    }

    const isPasswordValid = await bcryptjs.compare(password, user.password);

    if (!isPasswordValid) {
      throw new UnauthorizedException("Invalid password");
    }

    return {
      email: user.email,
    };
  }
}

auth controller

src\auth\auth.controller.ts

ts
import { Body, Controller, HttpCode, HttpStatus, Post } from "@nestjs/common";
import { LoginDto } from "./dto/login.dto";
import { RegisterDto } from "./dto/register.dto";

@Controller("auth")
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post("register")
  register(@Body() registerDto: RegisterDto) {
    return this.authService.register(registerDto);
  }

  @HttpCode(HttpStatus.OK)
  @Post("login")
  login(@Body() loginDto: LoginDto) {
    return this.authService.login(loginDto);
  }
}

@HttpCode(HttpStatus.OK)

El decorador @HttpCode(HttpStatus.OK) es una característica de Nest.js que te permite establecer explícitamente el código de estado HTTP que se enviará como respuesta para una ruta específica de un controlador. En este caso particular, el decorador está configurado para devolver un código de estado HTTP 200 (OK) cuando se realiza una solicitud POST a la ruta /login.

JWT

sh
yarn add @nestjs/jwt

src\auth\auth.service.ts

ts
import {
  BadRequestException,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { RegisterDto } from "./dto/register.dto";

import { JwtService } from "@nestjs/jwt";
import * as bcryptjs from "bcryptjs";
import { UsersService } from "src/users/users.service";
import { LoginDto } from "./dto/login.dto";

@Injectable()
export class AuthService {
  constructor(
    private readonly usersService: UsersService,
    private readonly jwtService: JwtService
  ) {}

  async login({ email, password }: LoginDto) {
    const user = await this.usersService.findOneByEmail(email);

    if (!user) {
      throw new UnauthorizedException("Invalid email");
    }

    const isPasswordValid = await bcryptjs.compare(password, user.password);

    if (!isPasswordValid) {
      throw new UnauthorizedException("Invalid password");
    }

    const payload = { email: user.email };

    const token = await this.jwtService.signAsync(payload);

    return {
      token: token,
      email: user.email,
    };
  }
}

src\auth\constants\jwt.constant.ts

ts
export const jwtConstants = {
  secret: "no utilizar en producción",
};

src\auth\auth.module.ts

ts
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";
import { UsersModule } from "src/users/users.module";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { jwtConstants } from "./constants/jwt.constant";

@Module({
  imports: [
    UsersModule,
    JwtModule.register({
      global: true,
      secret: jwtConstants.secret,
      signOptions: { expiresIn: "1d" },
    }),
  ],
  controllers: [AuthController],
  providers: [AuthService],
})
export class AuthModule {}

global

Estamos registrando el JwtModule como global para facilitarnos las cosas. Esto significa que no necesitamos importar JwtModule ningún otro lugar de nuestra aplicación.

Guard

En Nest.js, los guards son una funcionalidad que permite controlar si una determinada ruta o endpoint en una aplicación puede ser accedida por un cliente o no. Los guards se pueden utilizar para aplicar lógica de autenticación, autorización o cualquier otro tipo de validación antes de permitir que una solicitud llegue al controlador correspondiente.

sh
nest g guard auth --no-spec

canActive

El método canActivate es uno de los métodos principales que un guard puede implementar. Este método se ejecuta antes de que se procese una solicitud hacia una ruta o endpoint específico. La función canActivate devuelve un valor booleano o una promesa que resuelve a un valor booleano. Dependiendo del resultado de esta función, se permitirá o se denegará el acceso al endpoint protegido.

Si el método canActivate devuelve true, la solicitud se permite y continúa el flujo normal hacia el controlador asociado con la ruta.

Si el método canActivate devuelve false, se niega el acceso a la ruta protegida y la solicitud se detiene. En este caso, el guard puede tomar acciones como redirigir a una página de inicio de sesión o enviar una respuesta de error, según el caso.

src\auth\guard\auth.guard.ts

ts
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Request } from "express";

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    // El objeto context proporciona información
    // sobre la solicitud entrante y el entorno de ejecución.
    const request = context.switchToHttp().getRequest();
    console.log(request.headers.authorization);

    // Aquí puedes implementar tu lógica de autenticación o autorización.
    // Por ejemplo, verificar si el usuario está autenticado, si tiene los roles adecuados, etc.

    // Si la validación es exitosa, devuelve true, permitiendo el acceso.
    // Si la validación falla, devuelve false, denegando el acceso.

    return true; // o false, dependiendo de la lógica de tu guard.
  }

  private extractTokenFromHeader(request: Request) {
    const [type, token] = request.headers.authorization?.split(" ") ?? [];
    return type === "Bearer" ? token : undefined;
  }
}

src\auth\auth.controller.ts

ts
// import { Get, Request, UseGuards } from "@nestjs/common";
// import { AuthGuard } from "./guard/auth.guard";
Get('profile')
@UseGuards(AuthGuard)
profile(@Request() req) {
  return 'profile';
}

src\auth\guard\auth.guard.ts

ts
import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from "@nestjs/common";
import { JwtService } from "@nestjs/jwt";
import { Request } from "express";
import { jwtConstants } from "../constants/jwt.constant";

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly jwtService: JwtService) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    const token = this.extractTokenFromHeader(request);

    if (!token) {
      throw new UnauthorizedException();
    }

    try {
      const payload = await this.jwtService.verifyAsync(token, {
        secret: jwtConstants.secret,
      });
      request.user = payload;
    } catch (error) {
      throw new UnauthorizedException();
    }

    return true;
  }

  private extractTokenFromHeader(request: Request) {
    const [type, token] = request.headers.authorization?.split(" ") ?? [];
    return type === "Bearer" ? token : undefined;
  }
}

src\auth\auth.controller.ts

ts
@Get('profile')
@UseGuards(AuthGuard)
profile(@Request() req) {
  return req.user;
}