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.
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