En esta página
Guards y autorización basada en roles
¿Qué son los guards en NestJS?
Los guards son clases que implementan la interfaz CanActivate y tienen una sola responsabilidad: determinar si una petición debe ser procesada o rechazada. Son similares a los middlewares de Express, pero tienen acceso al contexto de ejecución de NestJS, lo que les permite saber qué controlador y método están a punto de ejecutarse.
La diferencia clave entre guards y middlewares es que los guards tienen acceso a la información de metadatos del controlador a través del ExecutionContext y el Reflector. Esto los hace ideales para autorización basada en roles y permisos.
El contrato CanActivate
Todo guard debe implementar el método canActivate:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
@Injectable()
export class MiGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> {
// Retorna true para permitir, false para denegar (lanza 403)
// O lanza una excepción HTTP específica para más control
return true;
}
}El método puede ser síncrono o asíncrono. Si retorna false, NestJS lanza automáticamente una ForbiddenException (403). Si quieres un código de error diferente (como 401), lanza la excepción explícitamente.
ExecutionContext — El contexto de ejecución
El ExecutionContext es el superconjunto del ArgumentsHost que proporciona información adicional sobre el manejador actual:
canActivate(context: ExecutionContext): boolean {
// Acceder al request de HTTP
const request = context.switchToHttp().getRequest<Request>();
const response = context.switchToHttp().getResponse<Response>();
// Obtener el nombre del controlador y el método
const controllerName = context.getClass().name;
const methodName = context.getHandler().name;
console.log(`${controllerName}.${methodName} fue llamado`);
return true;
}Aplicando guards
Los guards se pueden aplicar en tres niveles:
// Nivel de método
@Get('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
metodoProtegido() {}
// Nivel de controlador
@Controller('admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {}
// Nivel global — en app.module.ts (recomendado)
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{ provide: APP_GUARD, useClass: JwtAuthGuard },
{ provide: APP_GUARD, useClass: RolesGuard },
],
})
export class AppModule {}El patrón de guard global + @Public()
La práctica más recomendada es registrar el JwtAuthGuard globalmente y marcar las rutas públicas con un decorador @Public(). Así todas las rutas están protegidas por defecto:
// main.ts o app.module.ts
providers: [
{
provide: APP_GUARD,
useClass: JwtAuthGuard,
},
]
// Rutas que no requieren autenticación
@Controller('auth')
export class AuthController {
@Post('login')
@Public() // ← exento del guard global
login(@Body() loginDto: LoginDto) {}
@Post('registro')
@Public()
registro(@Body() registroDto: RegistroDto) {}
}Reflector — Leyendo metadatos de decoradores
El Reflector es el servicio de NestJS que permite leer los metadatos establecidos con SetMetadata. Es la pieza que conecta los decoradores de metadatos con los guards:
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
// getAllAndOverride busca primero en el handler, luego en la clase
// El primer valor encontrado tiene prioridad
const roles = this.reflector.getAllAndOverride<string[]>(ROLES_KEY, [
context.getHandler(), // nivel de método
context.getClass(), // nivel de controlador
]);
// getAllAndMerge combina los metadatos de handler Y clase
const todosLosRoles = this.reflector.getAllAndMerge<string[]>(ROLES_KEY, [
context.getHandler(),
context.getClass(),
]);
return true;
}
}Decoradores personalizados de parámetros
Además de los decoradores de metadatos, puedes crear decoradores de parámetros que extraen datos del request:
// decorators/ip-cliente.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';
export const IpCliente = createParamDecorator(
(_data: unknown, ctx: ExecutionContext): string => {
const request = ctx.switchToHttp().getRequest<Request>();
return (
(request.headers['x-forwarded-for'] as string)?.split(',')[0] ??
request.socket.remoteAddress ??
'desconocido'
);
},
);
// Uso en controlador
@Post('login')
@Public()
async login(
@Body() loginDto: LoginDto,
@IpCliente() ip: string,
) {
await this.auditoriaService.registrarIntento(loginDto.email, ip);
return this.authService.login(loginDto);
}Guard de suscripción activa
Un ejemplo real de guard de negocio —verificar si el usuario tiene una suscripción activa:
@Injectable()
export class SuscripcionActivaGuard implements CanActivate {
constructor(private readonly suscripcionesService: SuscripcionesService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const { user } = context.switchToHttp().getRequest<RequestWithUser>();
if (!user) {
throw new UnauthorizedException();
}
const suscripcion = await this.suscripcionesService.findByUsuario(user.sub);
if (!suscripcion || suscripcion.estado !== 'activa') {
throw new ForbiddenException(
'Esta funcionalidad requiere una suscripción activa. ' +
'Visita /planes para ver las opciones disponibles.'
);
}
return true;
}
}Guards en WebSockets y contextos no-HTTP
El ExecutionContext soporta múltiples contextos de transporte. Para guards que funcionen tanto en HTTP como en WebSockets:
@Injectable()
export class UniversalAuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const tipo = context.getType();
if (tipo === 'http') {
const req = context.switchToHttp().getRequest<Request>();
return this.validarToken(req.headers.authorization);
}
if (tipo === 'ws') {
const client = context.switchToWs().getClient<WebSocket>();
const data = context.switchToWs().getData<{ token: string }>();
return this.validarToken(`Bearer ${data.token}`);
}
return false;
}
private validarToken(authHeader: string | undefined): boolean {
if (!authHeader?.startsWith('Bearer ')) return false;
// lógica de validación del token JWT...
return true;
}
}Los guards son la tercera gran pieza del pipeline de NestJS, después de los pipes y antes de los interceptores. En la próxima lección comenzaremos a trabajar con bases de datos reales usando TypeORM —la ORM más popular del ecosistema NestJS.
Inicia sesión para guardar tu progreso