
Introducción a los Guards
Los Guards en NestJS son una característica fundamental del sistema de seguridad que permite controlar el acceso a rutas y recursos específicos de tu aplicación. Funcionan como filtros de autorización que se ejecutan antes de que una petición llegue al controlador, determinando si debe procesarse o rechazarse.
Un Guard implementa la interfaz CanActivate y debe devolver un valor booleano que indica si la petición actual está autorizada para continuar. Esta decisión puede basarse en diversos factores como la autenticación del usuario, sus permisos, roles o cualquier lógica de negocio específica.
Funcionamiento básico de los Guards
Los Guards se ejecutan después de los middlewares pero antes de los interceptors y pipes. Esta posición en el ciclo de vida de la petición los convierte en el lugar ideal para implementar lógica de autorización, ya que pueden detener el procesamiento antes de que se ejecute cualquier lógica de negocio.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
// Lógica de validación
return this.validateRequest(request);
}
private validateRequest(request: any): boolean {
// Ejemplo: verificar si existe un token de autorización
return request.headers.authorization !== undefined;
}
}
Tipos de Guards en NestJS
NestJS permite implementar diferentes tipos de Guards según las necesidades de tu aplicación:
Guards de autenticación: Verifican si el usuario está autenticado correctamente. Suelen validar tokens JWT, cookies de sesión o credenciales básicas.
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private jwtService: JwtService) {}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
return false;
}
try {
const payload = this.jwtService.verify(token);
request.user = payload;
return true;
} catch {
return false;
}
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}
Guards de autorización: Verifican si el usuario autenticado tiene los permisos necesarios para acceder a un recurso específico. Evalúan roles, permisos o políticas de acceso.
@Injectable()
export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const requiredRoles = this.reflector.getAllAndOverride<string[]>('roles', [
context.getHandler(),
context.getClass(),
]);
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
return requiredRoles.some((role) => user.roles?.includes(role));
}
}
Aplicación de Guards
Los Guards pueden aplicarse a diferentes niveles de tu aplicación, proporcionando flexibilidad en el control de acceso:
A nivel global: Afecta a todas las rutas de la aplicación.
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: AuthGuard,
},
],
})
export class AppModule {}
A nivel de controlador: Protege todas las rutas de un controlador específico.
@Controller('users')
@UseGuards(AuthGuard)
export class UsersController {
@Get()
findAll() {
return 'Esta ruta está protegida por el AuthGuard';
}
}
A nivel de método: Protege únicamente una ruta específica.
@Controller('users')
export class UsersController {
@Get('profile')
@UseGuards(AuthGuard, RolesGuard)
getProfile() {
return 'Ruta protegida por múltiples guards';
}
}
ExecutionContext y acceso a datos
El ExecutionContext proporciona acceso completo a los detalles de la petición actual. Este contexto permite a los Guards examinar la petición HTTP, extraer headers, parámetros y cualquier información necesaria para tomar decisiones de autorización.
@Injectable()
export class OwnershipGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user;
const resourceId = request.params.id;
// Verificar si el usuario es propietario del recurso
return this.checkOwnership(user.id, resourceId);
}
private checkOwnership(userId: string, resourceId: string): boolean {
// Lógica para verificar propiedad del recurso
return true; // Simplificado para el ejemplo
}
}
Los Guards representan una herramienta esencial para implementar seguridad robusta en aplicaciones NestJS. Su integración natural con el sistema de inyección de dependencias permite crear soluciones de autorización complejas y reutilizables que se adaptan perfectamente a los requisitos de seguridad de aplicaciones empresariales.
Decoradores @Public y @Roles con Reflector
En proyectos enterprise se aplica un guard JWT global y se exime mediante un decorador @Public():
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(
private readonly jwtService: JwtService,
private readonly reflector: Reflector,
) {}
canActivate(context: ExecutionContext): boolean {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) return true;
const request = context.switchToHttp().getRequest();
const token = request.headers.authorization?.split(' ')[1];
if (!token) throw new UnauthorizedException('Token ausente');
try {
request.user = this.jwtService.verify(token);
return true;
} catch {
throw new UnauthorizedException('Token inválido');
}
}
}
@Controller('auth')
export class AuthController {
@Public()
@Post('login')
login(@Body() dto: LoginDto) { /* ... */ }
}
Patrón secure-by-default: la API exige token salvo que el endpoint declare explícitamente que es público. Un olvido (no añadir @Public()) bloquea el endpoint, que es el comportamiento seguro en producción.
Guards asíncronos y autorización por dominio
canActivate puede devolver Promise<boolean> para consultar base de datos o un servicio externo. Caso típico: un usuario solo puede operar sobre recursos cuyo tenantId coincide:
@Injectable()
export class TenantGuard implements CanActivate {
constructor(private readonly resourceService: ResourceService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest();
const resourceId = req.params.id;
const userTenant = req.user.tenantId;
const resource = await this.resourceService.findById(resourceId);
if (!resource) throw new NotFoundException(`Recurso ${resourceId} no existe`);
if (resource.tenantId !== userTenant) {
throw new ForbiddenException('Recurso no pertenece a tu organización');
}
return true;
}
}
Combinación con interceptors y exception filters
Cuando el guard rechaza, NestJS lanza ForbiddenException por defecto. Un ExceptionFilter global puede normalizar la respuesta a formato RFC 7807 (Problem Details):
@Catch(HttpException)
export class ProblemDetailsFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception.getStatus();
response.status(status).json({
type: `https://api.example.com/errors/${exception.name}`,
title: exception.message,
status,
detail: exception.getResponse(),
instance: ctx.getRequest().url,
});
}
}
Tests de guards
Los guards se prueban aislados con un ExecutionContext mockeado:
import { createMock } from '@golevelup/ts-jest';
const guard = new RolesGuard(new Reflector());
const ctx = createMock<ExecutionContext>({
switchToHttp: () => ({ getRequest: () => ({ user: { role: 'user' } }) }),
getHandler: () => function () {},
getClass: () => class {},
});
jest.spyOn(Reflector.prototype, 'getAllAndOverride').mockReturnValue(['admin']);
expect(guard.canActivate(ctx)).toBe(false);
Probar guards aislados permite cubrir casos límite (token expirado, rol incorrecto, recurso de otro tenant) sin levantar el módulo entero.
Fuentes y referencias
Documentación oficial y recursos externos para profundizar en Nest
Documentación oficial de Nest
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Nest es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Nest
Explora más contenido relacionado con Nest y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Comprender qué son los Guards y su función en el ciclo de vida de una petición en NestJS. Aprender a implementar Guards personalizados que controlen la autenticación y autorización. Conocer los diferentes tipos de Guards y cuándo aplicarlos (autenticación, roles, propiedad). Saber cómo aplicar Guards a nivel global, de controlador y de método. Entender el uso de ExecutionContext para acceder a la información de la petición y tomar decisiones de seguridad.