📚 Módulo 05: Backend - APIRouters REST, CORS e Segurança JWT

Neste módulo, conectaremos a lógica de serviços com as portas de entrada da web. Desenvolveremos a Camada de Controladores (Controllers) do NestJS mapeando rotas REST completas para o catálogo, ativaremos as configurações de compartilhamento de origem CORS e programaremos uma suíte de segurança stateless utilizando tokens criptografados JWT (JSON Web Tokens) e guards customizados para gerenciar acessos por papéis (ADMIN e USER).


🔐 O Escudo Triplo (Autenticação e Autorização)

A arquitetura de segurança do NestJS é aplicada em camadas sucessivas antes da requisição tocar na regra de negócio.

flowchart TD
    classDef client fill:#61dafb,stroke:#20232a,stroke-width:2px,color:#000;
    classDef shield fill:#f59e0b,stroke:#b45309,stroke-width:2px,color:#fff;
    classDef danger fill:#ef4444,stroke:#991b1b,stroke-width:2px,color:#fff;
    classDef nest fill:#ea2845,stroke:#3b0713,stroke-width:2px,color:#fff;

    A[SPA React]:::client -->|POST /produtos + Token| B{AuthGuard JWT}:::shield
    
    B -- Token Ausente/Falso --> C[HTTP 401 Unauthorized]:::danger
    B -- Identidade Comprovada --> D{RolesGuard Admin?}:::shield
    
    D -- Usuário Comum --> E[HTTP 403 Forbidden]:::danger
    D -- É Administrador --> F[ProdutoController e Service]:::nest

🏛️ 1. O que são Controllers no NestJS?

Os Controllers são responsáveis por receber as requisições HTTP recebidas do cliente (React), extrair parâmetros da URL ou corpo, repassar os dados limpos para a camada de serviços e retornar a resposta serializada ao cliente.

Instale as dependências adicionais de criptografia e tokens no terminal do backend:

npm install @nestjs/jwt @nestjs/passport passport passport-jwt
npm install @types/passport-jwt --save-dev

Abaixo, programaremos o controlador do catálogo de produtos em src/produto/produto.controller.ts, expondo rotas públicas de consulta e rotas administrativas protegidas de alteração:

// src/produto/produto.controller.ts
import { Controller, Get, Post, Put, Delete, Body, Param, UseGuards, HttpStatus, HttpCode } from '@nestjs/common';
import { ProdutoService } from './produto.service';
import { CriarProdutoDto } from './dto/criar-produto.dto';
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
import { RolesGuard } from '../auth/roles.guard';
import { Roles } from '../auth/roles.decorator';

@Controller('produtos') // Rota base: /produtos
export class ProdutoController {
  constructor(private readonly produtoService: ProdutoService) {}

  @Get() // Rota pública: GET /produtos
  async listarTodos() {
    return this.produtoService.buscarTodos();
  }

  @Get(':id') // Rota pública: GET /produtos/:id
  async buscarPorId(@Param('id') id: string) {
    return this.produtoService.buscarPorId(Number(id));
  }

  @Post() // Rota protegida: POST /produtos
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN') // Apenas administradores podem cadastrar
  @HttpCode(HttpStatus.CREATED)
  async criar(@Body() dto: CriarProdutoDto) {
    return this.produtoService.criar(dto);
  }

  @Put(':id') // Rota protegida: PUT /produtos/:id
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN')
  async atualizar(@Param('id') id: string, @Body() dto: CriarProdutoDto) {
    return this.produtoService.atualizar(Number(id), dto);
  }

  @Delete(':id') // Rota protegida: DELETE /produtos/:id
  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remover(@Param('id') id: string) {
    await this.produtoService.deletar(Number(id));
  }
}

🌐 2. Ativando o CORS no NestJS

Para permitir que a nossa aplicação React instalada em um domínio CDN de testes acesse a API NestJS sem ser bloqueada pelo navegador, devemos habilitar as configurações do CORS no bootstrap da aplicação:

// src/main.ts (Trecho de ativação de CORS)
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // Ativação explícita de CORS
  app.enableCors({
    origin: '*', // Em produção, substitua por seu domínio Netlify: ['https://tecloja.netlify.app']
    methods: 'GET,HEAD,PUT,PATCH,POST,DELETE,OPTIONS',
    credentials: true,
  });

  app.useGlobalPipes(new ValidationPipe({ whitelist: true }));
  await app.listen(process.env.PORT || 3000);
}

🔐 3. Segurança Stateless com JWT e Guards por Papéis

Na arquitetura REST stateless, o servidor não armazena sessões em memória. Em vez disso, após o login bem-sucedido do usuário, retornamos um token JWT contendo o ID e o papel do usuário. Este token deve ser enviado no cabeçalho Authorization: Bearer <TOKEN> em cada nova requisição segura.

A. Estratégia JWT com Passport

Crie a estratégia de verificação do JWT em src/auth/jwt.strategy.ts:

// src/auth/jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET || 'chave_secreta_padrao',
    });
  }

  async validate(payload: any) {
    // Retorna os dados desempacotados do token que serão anexados no objeto "request.user"
    return { id: payload.sub, username: payload.username, role: payload.role };
  }
}

Crie o guarda de verificação de autenticação padrão em src/auth/jwt-auth.guard.ts:

// src/auth/jwt-auth.guard.ts
import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

B. Proteção Baseada em Regras (Role-Based Authorization)

Para criar a anotação @Roles(...) que restringe o acesso aos métodos lógicos, criaremos um Custom Decorator em src/auth/roles.decorator.ts:

// src/auth/roles.decorator.ts
import { SetMetadata } from '@nestjs/common';

export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

Agora, programaremos o guarda lógico de validação de papéis em src/auth/roles.guard.ts, que lê os metadados da rota e compara com o papel contido no JWT decodificado:

// src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RolesGuard implements CanActivate {
  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    // 1. Obter papéis necessários exigidos pela rota
    const rolesRequeridas = this.reflector.get<string[]>('roles', context.getHandler());
    if (!rolesRequeridas) {
      return true; // Se a rota não declarar @Roles, ela é pública por papel
    }

    // 2. Obter dados do usuário injetados anteriormente pelo JwtAuthGuard
    const request = context.switchToHttp().getRequest();
    const usuario = request.user;

    if (!usuario || !usuario.role) {
      throw new ForbiddenException('Acesso negado: Usuário sem papel de segurança definido.');
    }

    // 3. Verificar se o papel do usuário satisfaz alguma das permissões da rota
    const possuiPermissao = rolesRequeridas.includes(usuario.role);
    if (!possuiPermissao) {
      throw new ForbiddenException('Acesso negado: Permissão insuficiente para esta operação.');
    }

    return true;
  }
}

C. Módulo de Autenticação e Login

Abaixo, criaremos o controlador de login em src/auth/auth.controller.ts para receber e-mail/senha e gerar o token de acesso:

// src/auth/auth.controller.ts
import { Controller, Post, Body, UnauthorizedException, HttpCode, HttpStatus } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/login.dto';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('login')
  @HttpCode(HttpStatus.OK)
  async login(@Body() dto: LoginDto) {
    const usuario = await this.authService.validarUsuario(dto.email, dto.password);
    if (!usuario) {
      throw new UnauthorizedException('E-mail ou senha incorretos.');
    }
    return this.authService.gerarToken(usuario);
  }
}


✅ Pré-Requisitos deste Módulo

Antes de passar para a criação das telas React e o setup do frontend, certifique-se de que:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. Swagger e Endpoints Visíveis: Suba a API localmente e certifique-se de que todos os caminhos de /produtos, /pedidos e /auth respondem a requisições locais.
  2. Barreira de Segurança Operando: Tente disparar um POST em /produtos sem passar cabeçalhos de autenticação. A API NestJS deve retornar o código de erro 401 Unauthorized com a assinatura JSON do nosso Filtro de Exceções.
  3. Bloqueio de Papel: Autentique-se com um usuário semeado que possua o cargo USER (ex: cliente@tecloja.com). Tente usar o token recebido para criar um produto. A API deve retornar 403 Forbidden sinalizando permissão insuficiente.

⚠️ Erros Comuns

Erro Causa Solução
O objeto request.user chega como indefinido dentro do RolesGuard O guard RolesGuard foi invocado sem o JwtAuthGuard correspondente ou foi declarado antes dele na anotação @UseGuards(). Declare sempre os guards na ordem correta: @UseGuards(JwtAuthGuard, RolesGuard), pois a descriptografia e injeção do usuário na requisição ocorrem na etapa do Passport.
Chamadas AJAX do React retornando erro de CORS no console do navegador O endereço IP ou a porta do frontend React não foi liberada na whitelist do CORS do NestJS. Verifique qual endereço o Vite local está usando e adicione-o explicitamente no array de origens da diretiva app.enableCors({ origin: ... }) no arquivo src/main.ts.
O segredo do token (JWT_SECRET) falha ao validar em produção O carregador de configurações do NestJS não encontrou o arquivo .env na nuvem ou a variável de ambiente não foi propagada. Certifique-se de declarar as variáveis de ambiente diretamente nas configurações de serviço da plataforma de hospedagem (Render) correspondentes ao arquivo .env de testes.

🏁 Conclusão

Com os APIRouters criados e a segurança de papéis e tokens JWT configurada com sucesso, finalizamos de forma impecável o desenvolvimento da API de backend no ecossistema NestJS!

No Módulo 06, mudaremos o foco para o Frontend (Repositório 2). Inicializaremos a aplicação cliente com React 18 + Vite + TypeScript, mapearemos as interfaces TypeScript DTO equivalentes para dados unificados e configuraremos as rotas dinâmicas da Single Page Application com React Router utilizando recursos de lazy-loading para maximizar a performance de carregamento inicial.


Voltar para o Sumário