Creación de Endpoints Seguros con JWT en NestJS
Clase 16 de 22 • Curso de NestJS: Autenticación con Passport y JWT
En la clase anterior vimos como puedes hacer para identificar un rol y de acuerdo a eso poder darle acceso a un endpoint, ahora te pongo el siguiente escenario:
¿Como crear un endpoint que retorne todas las órdenes relacionados con el usuario que tiene sesión?
Una solución válida a este problema es que puedes crear un endpoint que enviándole el ID de un usuario te retorne dichas órdenes, algo como esto:
http://localhost:3000/users/1/orders
Este endpoint me retornaría todas las órdenes del usuario con ID 1, sin embargo puede ser algo peligroso, es decir, este endpoint solo debería estar disponible para los roles administrativos, pero no para los usuarios porque si conoces el ID de algún cliente podrías obtener la información de órdenes de compra de un usuario.
La solución más práctica para este caso es crear un endpoint de este tipo:
http://localhost:3000/profile/my-orders
Como ves en el endpoint anterior no estás colocando de forma explícita el ID de un usuario. ¿Entonces como sabe a qué usuario le pertenecen las órdenes? Sencillo, sí nuestro usuario ya tiene una sesión, esta información ya está en el JWT que se le otorgó a ese usuario al autentificarse en el sistema así con eso podemos inferirlo de acuerdo a la sesión
Ok, ok, cerebrito, suena bien pero, ¿y el código...? Vamos a iniciar con ello. Lo primero es que vamos a hacer es crear un nuevo controlador para todos las solicitudes que sean de este tipo. Lo vamos a crear con el nombre ProfileController
.
nest g co users/controllers/profile --flat
Allí vamos a exponer un nuevo endpoint que va a recibir la solicitud y gracias al decorador de
@UseGuards(JwtAuthGuard, RolesGuard)
nos aseguramos que tenga un token válido, así que en ese método recogemos la información de usuario que tiene esa sesión así:
@Roles(Role.CUSTOMER) @Get('my-orders') getOrders(@Req() req: Request) { const user = req.user as PayloadToken; return this.orderService.findOne(user.sub); }
Simplemente con decirle que dentro del método que queremos la información del Request podemos obtener la información del usuario que tiene sesión, luego simplemente obtenemos el ID y ya con eso podemos crear un método en nuestro servicio de órdenes que las retorne, algo así:
ordersByCustomer(customerId: number) { return this.orderRepo.find({ where: { customer: customerId, }, }); }
Este sería el código completo del nuevo controlador ProfileController
:
import { Controller, Get, UseGuards, Req } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { Request } from 'express'; import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { RolesGuard } from '../../auth/guards/roles.guard'; import { Roles } from '../../auth/decorators/roles.decorator'; import { Role } from '../../auth/models/roles.model'; import { PayloadToken } from 'src/auth/models/token.model'; import { OrdersService } from '../services/orders.service'; @UseGuards(JwtAuthGuard, RolesGuard) @ApiTags('profile') @Controller('profile') @Controller('profile') export class ProfileController { constructor(private orderService: OrdersService) {} @Roles(Role.CUSTOMER) @Get('my-orders') getOrders(@Req() req: Request) { const user = req.user as PayloadToken; return this.orderService.ordersByCustomer(user.sub); } }
Puedes ver toda esta solución en la rama 14.