Controlar el acceso a los endpoints de una aplicación es fundamental, y en NestJS los guardianes ofrecen una forma elegante de hacerlo. Pero, ¿qué sucede cuando necesitas que ciertos endpoints sean públicos mientras el resto permanece protegido? La respuesta está en combinar guardianes con metadata y decoradores personalizados, logrando un código más flexible y mantenible.
¿Cómo proteger todos los endpoints de un controlador con un guardián?
Cuando se mueve el decorador @UseGuards() al nivel de la clase completa, todos los endpoints dentro de ese controlador quedan protegidos automáticamente [01:00]. Esto significa que cada ruta, ya sea getHello, newEndpoint o cualquier otra, requerirá cumplir con la validación definida en el guardián, por ejemplo, enviar un header de autorización con la API key correcta.
Sin embargo, en la práctica no siempre queremos que absolutamente todos los endpoints exijan autenticación. Algunos necesitan ser de acceso libre, y ahí es donde entra en juego la metadata.
¿Qué es setMetadata y cómo permite crear endpoints públicos?
El decorador setMetadata se importa desde @nestjs/common [02:25] y permite inyectar información adicional al contexto de un endpoint. Se usa de la siguiente manera:
- Se define un nombre para la metadata, por ejemplo
isPublic.
- Se le asigna un valor, en este caso
true.
- Se coloca como decorador sobre el método del controlador que se desea hacer público.
De esta forma, aunque el guardián se aplique a todo el controlador, los endpoints marcados con @SetMetadata('isPublic', true) podrán ser identificados como públicos.
¿Cómo lee el guardián la metadata del endpoint?
Para que el guardián pueda leer la metadata, se utiliza la clase Reflector, que se importa desde @nestjs/core [03:32]. Este servicio se inyecta en el constructor del guardián mediante inyección de dependencias:
typescript
constructor(private reflector: Reflector) {}
Dentro del método canActivate, se obtiene el valor de la metadata así [04:05]:
typescript
const isPublic = this.reflector.get<boolean>('isPublic', context.getHandler());
if (isPublic) {
return true;
}
- Si
isPublic es true, el guardián retorna true directamente sin validar el header, permitiendo el acceso libre.
- Si no existe esa metadata o es
false, se ejecuta la lógica habitual de validación del token.
Es importante que el nombre de la metadata coincida exactamente con el que se definió en el decorador; de lo contrario, el Reflector no podrá obtenerla.
¿Cómo se comportan las rutas públicas y protegidas?
Al probar en Insomnia [05:15], el resultado es claro:
- Los endpoints marcados como públicos responden sin necesidad de enviar ningún header.
- Los endpoints sin esa metadata devuelven un error de autorización si no se incluye el token válido.
- Al agregar el header correcto, los endpoints protegidos permiten el acceso normalmente.
¿Por qué crear un decorador personalizado para rutas públicas?
Usar @SetMetadata('isPublic', true) directamente tiene un riesgo: escribir mal el nombre de la metadata en algún punto del código. Para evitar este problema y hacer el código más mantenible, se recomienda crear un decorador personalizado [07:05].
Se crea un archivo dentro de una carpeta decorators en el módulo de autenticación, por ejemplo public.decorator.ts:
typescript
import { SetMetadata } from '@nestjs/common';
export const IS_PUBLIC_KEY = 'isPublic';
export const Public = () => SetMetadata(IS_PUBLIC_KEY, true);
- La constante
IS_PUBLIC_KEY centraliza el nombre de la metadata en un solo lugar.
- El decorador
@Public() encapsula la lógica de SetMetadata con el valor true por defecto.
En el guardián, se importa IS_PUBLIC_KEY desde el mismo archivo [09:10], reemplazando el string literal:
typescript
const isPublic = this.reflector.get<boolean>(IS_PUBLIC_KEY, context.getHandler());
Ahora, en el controlador basta con usar @Public() sobre cualquier método que deba ser de acceso libre. Si el nombre de la key cambia en el futuro, solo se modifica en un lugar y todos los guardianes y controladores que la referencien se actualizan automáticamente.
Al probar nuevamente [09:55], el comportamiento es idéntico: las rutas marcadas con @Public() son de acceso libre, y el resto requiere el header de autorización. La ventaja real está en la mantenibilidad y la consistencia del código a lo largo de toda la aplicación.
¿Has implementado decoradores personalizados en tus proyectos con NestJS? Comparte tu experiencia y cuéntanos qué otros patrones te han resultado útiles.