Crea una cuenta o inicia sesión

¡Continúa aprendiendo sin ningún costo! Únete y comienza a potenciar tu carrera

Creación de Guardianes en NestJS para Autorización de Endpoints

4/22
Recursos

¿Qué son los guardianes en NestJS y cómo funcionan?

Los guardianes son uno de los componentes más importantes en el framework NestJS, junto con los controladores, proveedores y pipes. Estos elementos son cruciales para asegurar que cada endpoint de un controlador tenga o no la autorización necesaria. En esencia, los guardianes permiten validar cualquier tipo de información que venga en los headers, como tokens de API, o cualquier otro estado requerido, antes de conceder acceso a un recurso solicitado.

¿Cómo crear y estructurar un guardián?

Para crear un guardián, primero se debe crear un módulo específico para manejar la autenticación dentro de la aplicación. Independientemente del proyecto en el que te encuentres, ya sea Mongo o cualquier otro, el proceso inicia con la generación de un nuevo módulo que administrará la autenticación. Dentro de este módulo, utilizaremos el generador de NestJS para crear un guardián.

nest generate guard guardianes/apk --skipTests

Aquí estamos indicando al generador que cree un guardián llamado apk dentro de una carpeta específica llamada guardianes.

Explorando el guardián generado

El guardián generado por defecto incluye un método que se encarga de decidir si se concede el acceso o no. Este método puede regresar un valor booleano, una promesa que regrese un booleano, o un observable, dependiendo de la implementación:

canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean> {
  return true; // true para permitir acceso, false para denegarlo
}

Configurando el guardián en un controlador

Para proteger un endpoint, primero importamos el guardián en el controlador correspondiente:

import { UseGuards } from '@nestjs/common';
import { Apkguarian } from './guardianes/apk.guard';

@Controller('api')
export class AppController {
  @Get('nuevo')
  @UseGuards(Apkgurar)
  nuevo() {
    return 'Acceso concedido';
  }
}

Ahora, el endpoint nuevo está protegido por el guardián, el cual por defecto no permite el acceso, simulando un valor false.

Validación dinámica con headers

Supongamos que queremos otorgar acceso solo si un header específico está presente en la solicitud. Para esto, implementamos la lógica necesaria en el método canActivate:

canActivate(context: ExecutionContext): boolean {
  const request = context.switchToHttp().getRequest();
  const authHeader = request.headers['authorization'];

  if (authHeader === '1234') {
    return true;
  } else {
    throw new UnauthorizedException('No estás autorizado');
  }
}

Este código revisa si el header authorization tiene el valor 1234 antes de permitir el acceso.

Probando en Insomnia

Si probamos con Insomnia o cualquier cliente REST, observa que cuando el header authorization tiene el valor correcto, se concede el acceso. De lo contrario, se lanza una excepción indicando que el acceso no está autorizado.

::tip:: Consejo: Personaliza los mensajes de error para que sean más claros y comprensibles. Usar excepciones específicas como UnauthorizedException en NestJS facilita esta tarea.

Aprender sobre los guardianes y cómo aplicarlos en tus proyectos es fundamental para tener una aplicación NestJS segura y robusta. Experimenta con diferentes condiciones y mecanismos de validación, y sigue aprendiendo sobre cómo mejorar la seguridad en tus aplicaciones. ¡Sigue adelante y no te detengas!

Aportes 8

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Por si alguno tiene dudas sobre qué se puede hacer una vez el proyecto esté completado…
Les dejo un proyecto propio de fake ecommerce que usa como base esta API (tiene modificaciones y detalles para adaptarla a mi proyecto). El frontend está hecho con React
Link aquí
Saludos a todos

Lo que hicimos

Esto aplica para ambos proyectos

nest g mo auth
nest g gu auth/guards/apiKey --flat

Configuración del guard

import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common';
import { Observable } from 'rxjs';
import { Request } from 'express';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const authHeader = request.header('Auth');
    const isAuth = authHeader === '1234';
    if (!isAuth) {
      throw new UnauthorizedException('not allow');
    }
    return true;
  }
}

Protegiendo una ruta

@UseGuards(ApiKeyGuard)
  @Get('nuevo')
  newEndpoint() {
    return 'yo soy nuevo';
  }

Introducción a Guards

Los guards (guardianes) son artefactos proporcionados por Nest.js que nos permiten proteger nuestros Endpoints en los controladores, verificando si un usuario tiene los permisos necesarios para acceder a un Endpoint en función de una condición.

Esto se puede lograr validando datos en los encabezados (headers), el estado, un token, etc., y determinando si el usuario tiene los permisos necesarios o no.

Creando la capa de autenticación

Primero lo que vamos a hacer es crear un nuevo guardián que se va a encargar de la autenticación en nuestra aplicación.

nest g gu auth/guards/api-key --flat

La estructura que nos debería crear es la siguiente:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    // Si retornamos true, se permite el acceso al Endpoint
    return false;
  }
}

Ahora, veamos cómo podemos implementar este guardián. Vamos a seleccionar un controlador y un Endpoint específico, y lo protegeremos con nuestro guardián:

  • **src/app-controller.ts**:
// Importamos el guardián y el decorador para usarlo
import { UseGuards } from '@nestjs/common';
import { ApiKeyGuard } from './auth/guards/api-key.guard';

@Controller()
export class AppController {
  // ...

  // Pasamos el guardián al decorador
  @UseGuards(ApiKeyGuard)
  @Get('nuevo')
  newEndpoint() {
    return 'Yo soy nuevo';
  }
}

De esta manera, no será posible acceder al Endpoint, ya que en el guardián estamos retornando false, lo cual deniega el acceso.

Podemos permitir el acceso si se envía un encabezado específico. Veamos cómo hacerlo en nuestro guardián:

// Importamos el tipo 'Request'
import { Request } from 'express';

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    // Obtenemos la información del request HTTP
    const request = context.switchToHttp().getRequest<Request>();
    // Obtenemos el encabezado 'Auth' de ese request
    const authHeader = request.header('Auth');

    // Si no se envía ninguna autenticación, se rechaza el acceso
    if (!authHeader) {
      return false;
    }
    
    // Verificamos si el encabezado 'Auth' es igual a 'contraseña ultra secreta'
    // Si es así, se permite el acceso; de lo contrario, se deniega el acceso
    return authHeader === 'contraseña ultra secreta';
  }
}

Si enviamos la clave correcta en el encabezado ‘Auth’, se nos permitirá el acceso; de lo contrario, recibiremos un código 403, que indica que no estamos autorizados.

De esta manera, podemos utilizar los guardián en Nest.js. Como has visto, los guardián se basan en un contexto que nos permite acceder a la información necesaria y podemos programar la condición que debe cumplir esta información obtenida a través del contexto.

me gustan las clases de Nicolas, pero esta nueva metodología de retomar código de otros cursos no.

Por cierto, el @UseGuard( … ) hay que importarlo de
import { …, UseGuards } from ‘@nestjs/common’;

4 artefactos principales en el framework


Controllers

Providers

Pipes

Guards


Los Guards nos ayudarán a brindar cada uno de los endpoints en el controller, y nos dirá si tiene o no autorización.


Ejecutamos nest g mo auth para crear un módulo nuevo, y generamos el guard con nest g gu auth/guards/apiKey —flat

Nest es muy hermoso! Que gran framework!!! ❤️

Error usando guards y @Res en el mismo Endpoint
Quiero compartir un error que tuve al estar usando un guard y @Res en el mismo endpoint.

Guard

@Injectable()
export class ApiKeyGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest<Request>();
    const authHeader = request.header('auth') || '';
    if (authHeader !== '123456') {
      throw new UnauthorizedException('User Unathorized');
    }
    return true;
  }
}

Controller


  @HttpCode(HttpStatus.ACCEPTED) 
  @Get('new')
  @UseGuards(ApiKeyGuard)
  newEndpoint(@Res() res: Response) {
    return res.json({ message: 'new world!' });
  }

Según mi teoría al estar usando el mismo request en el guard y el controller causaba este error

Error: This is caused by either a bug in Node.js or incorrect usage of Node.js internals.
node_app_dev  | Please open an issue with this stack trace at https://github.com/nodejs/node/issues
node_app_dev  | 
node_app_dev  |     at new NodeError (node:internal/errors:387:5)
node_app_dev  |     at assert (node:internal/assert:14:11)
[3:22:57 PM] File change detected. Starting incremental compilation...
[3:23:13 PM] File change detected. Starting incremental compilation...

Y se soluciono simplmente cambiando la forma de la respuesta en el controller

  @HttpCode(HttpStatus.ACCEPTED) // 👈 Using decorator
  @Get('new')
  @UseGuards(ApiKeyGuard)
  newEndpoint() {
    return { message: 'new world!' };
  }

Por defecto la respuesta se manda como JSON así que no es necesario usar res.json().

Vengo de express y por eso estaba tratando de usar esa misma forma.