Estrategia de autenticación JwtStrategy

Avanzado
Nest
Nest
Actualizado: 10/06/2025

¡Desbloquea el curso completo!

IA
Ejercicios
Certificado
Entrar

Introducción

Una vez que los usuarios pueden registrarse e iniciar sesión, el siguiente paso crítico es proteger las rutas de tu API para que solo los usuarios autenticados y autorizados puedan acceder a ellas. Utilizaremos Passport con una estrategia JWT para lograr esto en NestJS.

Se instala de la siguiente manera:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt
# O
yarn add @nestjs/jwt @nestjs/passport passport passport-jwt

PassportStrategy permite definir estrategias personalizadas de autenticación, como por ejemplo gestionar tokens JWT que llegan de frontend.

Crear estrategia JWT

La estrategia JWT es una clase que define cómo se extraerá el token de la solicitud y cómo se validará. Se encarga de decodificar el token y de verificar que el usuario asociado a ese token es válido.

Primero, definamos la estructura del payload que esperamos en el token decodificado. Esto debería coincidir con el payload que firmas en tu método de login (AuthService).

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;

}

Ahora, creamos nuestra estrategia JWT:

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'; // Importa el DTO del payload
import { ConfigService } from '@nestjs/config'; // Importa ConfigService

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    private configService: ConfigService, // Inyecta ConfigService para leer el secreto
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // Extrae el token del header 'Authorization: Bearer <token>'
      ignoreExpiration: false, // Asegura que el token expire
      secretOrKey: configService.get<string>('JWT_SECRET'), // Lee la clave secreta de las variables de entorno
    });
  }

  /**
   * Este método `validate` es llamado automáticamente por Passport después de que el token JWT ha sido validado.
   * Su función es cargar el usuario basado en el payload del token y adjuntarlo al objeto `request`.
   */
  async validate(payload: JwtPayload): Promise<User> {
    const { sub: id, email } = payload; // Extrae el ID y el email del payload

    // 1. Buscar el usuario en la base de datos
    const user = await this.userRepository.findOne({ where: { id, email } });

    // 2. Si el usuario no existe o es inválido, lanza una excepción de no autorizado
    if (!user) {
      throw new UnauthorizedException('Credenciales de token inválidas.');
    }

    // 3. Eliminar la contraseña del objeto de usuario antes de devolverlo por seguridad
    // Así, la contraseña cifrada no estará disponible en `request.user`
    delete user.password;

    // 4. Devolver el objeto usuario. Este objeto estará disponible en `request.user`
    return user;
  }
}

Configuración del AuthModule y AppModule

Para mantener la modularidad y las buenas prácticas, la JwtStrategy debe ser provista y, si es necesario, exportada desde tu AuthModule. Luego, el AppModule solo necesita importar AuthModule.

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 { JwtStrategy } from './strategies/jwt.strategy'; // Importa la estrategia JWT
import { ConfigService } from '@nestjs/config'; // Importa ConfigService

@Module({
  imports: [
    TypeOrmModule.forFeature([User]),
    PassportModule,
    // JwtModule se configura aquí también para que AuthService pueda firmar tokens
    JwtModule.registerAsync({
      imports: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        secret: configService.get<string>('JWT_SECRET'),
        signOptions: { expiresIn: '7d' },
      }),
      inject: [ConfigService],
    }),
  ],
  controllers: [AuthController],
  providers: [
    AuthService,
    JwtStrategy, // Provee la estrategia JWT en este módulo
  ],
  exports: [
    AuthService, // Exporta AuthService si otros módulos lo van a usar
    JwtStrategy, // Exporta JwtStrategy si otros módulos o guardias lo van a usar
    PassportModule, // Exporta PassportModule si otros módulos lo van a usar para guardias
  ],
})
export class AuthModule {}

src/app.module.ts

 Tu AppModule ahora solo necesita importar AuthModule (si las configuraciones de JWT y Passport ya están dentro de AuthModule y se exportan).

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthModule } from './auth/auth.module'; // Importa el 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}'], // Auto-descubrimiento de entidades
        synchronize: true, 
      }),
      inject: [ConfigService],
    }),
    AuthModule, // Importa el AuthModule
    // Si tienes otros módulos (ej. TasksModule, ProductsModule) impórtalos aquí también
  ],
  // controllers y providers ya no necesitan la configuración de JWT/Passport/TypeORM forFeature
  // porque el AuthModule se encarga de eso.
})
export class AppModule {}

Aplicar la Estrategia JWT en Controladores

Una vez que la estrategia está activa y provista, puedes aplicarla a cualquier método o controlador usando el decorador @UseGuards() y AuthGuard('jwt').

Vamos a crear un ejemplo en un BookController. Asumimos que tienes una entidad Book y un BookController y BookService configurados en un BookModule similar a los ejemplos anteriores.

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

// src/book/book.controller.ts
import {
    Controller,
    Get,
    Post,
    Body,
    UseGuards,
    Request,
    UnauthorizedException,
    HttpStatus,
    HttpCode,
    Param,
    ParseIntPipe,
} from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { BookService } from './book.service';
import { CreateBookDto } from './create-book.dto';
import { Book } from './book.entity';
import { Role } from '../user/role.enum';
import { User } from '../user/user.entity';

@Controller('books')
export class BookController {
    constructor(private readonly bookService: BookService) { }

    @Post()
    @HttpCode(HttpStatus.CREATED)
    @UseGuards(AuthGuard('jwt'))
    async create(
        @Request() req: { user: User },
        @Body() createBookDto: CreateBookDto
    ): Promise<Book> {
        console.log('Usuario identificado:', req.user.email, 'con rol:', req.user.role);

        // Verificar permisos de administrador
        if (req.user.role !== Role.ADMIN) {
            throw new UnauthorizedException('No tienes permisos para crear libros.');
        }

        return this.bookService.create(createBookDto, req.user.id);
    }

    @Get()
    async findAll(): Promise<Book[]> {
        return this.bookService.findAll();
    }

    @Get(':id')
    async findOne(@Param('id', ParseIntPipe) id: number): Promise<Book> {
        return this.bookService.findOne(id);
    }

    // Endpoint protegido solo para usuarios autenticados
    @Get('my-books')
    @UseGuards(AuthGuard('jwt'))
    async getMyBooks(@Request() req: { user: User }): Promise<Book[]> {
        return this.bookService.findByCreatedBy(req.user.id);
    }
}

Acceder al Usuario Autenticado en el Frontend

Una vez que un usuario ha iniciado sesión y su token JWT es válido, la JwtStrategy adjunta el objeto user (sin la contraseña) a la solicitud. Puedes acceder a este objeto en tus controladores.

Para que el frontend obtenga la información del usuario autenticado, puedes crear un endpoint como este:

// src/user/user.controller.ts
import { Controller, Get, UseGuards, Request } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { User } from './user.entity'; // Importa la entidad User para el tipo de request.user

@Controller('user')
export class UserController {

  @Get('account')
  @UseGuards(AuthGuard('jwt')) // Este endpoint requiere un token JWT válido
  public getAuthenticatedUserAccount(@Request() req: { user: User }): User {
    // `req.user` contiene el objeto usuario devuelto por el método `validate` de JwtStrategy
    return req.user;
  }
}

De este modo, una petición GET a http://localhost:3000/user/account retornará el objeto del usuario actualmente autenticado (sin su contraseña cifrada).

Verificar estrategia JWT

Realiza un Login: Envía una petición POST a http://localhost:3000/auth/login con credenciales válidas para obtener tu accessToken.

Copia el Token: Guarda el valor de accessToken que recibes.

Desde Postman se envía un libro al método create del book.controller.ts.

Primero se escribe un token válido en la cabecera Authorization tras haber hecho login con un usuario administrador:

Segundo se envía en el Body el nuevo libro:

{
  "title": "Nuevo libro",
  "isbn": "1234567890",
  "numPages": 300,
  "published": true,
  "price": 25.99,
  "author": {
    "id": 1,
    "firstName": "Gabriel",
    "lastName": "García Márquez",
    "birthDate": "1927-03-06"
  }
}

El resultado debería tener un status 201 Created.

Aprendizajes de esta lección

  • Recibir token JWT en backend
  • Verificar la firma del token JWT
  • Securizar endpoints
  • 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

⭐⭐⭐⭐⭐
4.9/5 valoración