📚 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:
- As regras transacionais e o serviço de checkout de pedidos foram criados no Módulo 04.
- As dependências
@nestjs/jwt,@nestjs/passport,passportepassport-jwtestão instaladas no repositório do backend.
🤔 Por que fizemos assim?
- Por que delegar o controle de rotas à Camada de Controladores (
Controllers)? Controladores atuam como a porta de entrada da rede para a API. Anotá-los com@Controllerdesacopla a infraestrutura HTTP da lógica interna de negócios contida nos serviços. O NestJS serializa as respostas de sucesso em JSON automaticamente e permite definir códigos de retorno HTTP customizados (como201 Createdou204 No Content) de forma totalmente declarativa. - Por que adotar autenticação Stateless por token JWT em vez de Sessões no Servidor? Em arquiteturas modernas de nuvem (com deploys em containers independentes da Render), manter sessões em memória impede a escalabilidade horizontal do backend. O JWT criptografa os dados do usuário (como ID e cargo) em um payload assinado digitalmente pela chave secreta do servidor. Isso torna a validação stateless, permitindo autenticar requisições subsequentes apenas validando a assinatura do token enviado no cabeçalho
Authorization: Bearer <token>, sem tocar na memória física ou no banco. - Por que encadear Guards (
JwtAuthGuardeRolesGuard) para controle de acessos? A segurança em camadas limpa o código dos controladores. OJwtAuthGuardexecuta primeiro, validando o token JWT e injetando as credenciais no objeto da requisição (request.user). Em seguida, oRolesGuardconsome esses dados comparando o papel do usuário logado com os metadados requeridos pela rota (extraídos peloReflectora partir do decorator@Roles('ADMIN')), rejeitando com403 Forbiddenacessos indevidos antes que a rota atinja os controladores.
🔍 Checkpoint
- Swagger e Endpoints Visíveis: Suba a API localmente e certifique-se de que todos os caminhos de
/produtos,/pedidose/authrespondem a requisições locais. - Barreira de Segurança Operando: Tente disparar um
POSTem/produtossem 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. - 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.