📚 Módulo 05: Backend - Autenticação e Autorização (JWT)
A nossa API consegue faturar pedidos, mas as rotas de criação e exclusão de produtos não estão protegidas. Se qualquer usuário mal-intencionado descobrir a rota POST /produtos, ele adicionará itens fictícios no nosso banco.
Implementaremos a camada de Segurança do NestJS utilizando passport-jwt e Guards.
🔐 1. O Fluxo de Autenticação JWT
O NestJS possui uma proteção nativa chamada Guards (@UseGuards), que age interceptando a requisição antes mesmo dela chegar ao Controlador.
flowchart TD
A[BFF do Next.js] -->|1. POST /auth/login| B(AuthService)
B -->|2. Valida Senha (Bcrypt)| C[(PostgreSQL)]
B -->|3. Assina JWT (Secret)| D[Retorna Token]
D --> A
A -->|4. POST /produtos + Header Bearer| E{JwtAuthGuard}
E -- Token Válido --> F{RolesGuard Admin?}
E -- Token Falso/Expirado --> G[HTTP 401 Unauthorized]
F -- Não é Admin --> H[HTTP 403 Forbidden]
F -- É Admin --> I[ProdutoController]
Instalação de Dependências
npm install @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt
npm install @types/passport-jwt @types/bcrypt --save-dev
Gerando os módulos:
npx nest g module auth
npx nest g controller auth
npx nest g service auth
🔑 2. Módulo de Autenticação
src/auth/auth.module.ts
Configure o JWT globalmente. Em produção, você guardaria a “secret” no arquivo .env!
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';
@Module({
imports: [
PassportModule,
JwtModule.register({
secret: 'MINHA_CHAVE_SUPER_SECRETA_TECLOJA_2024',
signOptions: { expiresIn: '1d' }, // Expira em 1 dia
}),
],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}
O Serviço de Validação (auth.service.ts)
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { JwtService } from '@nestjs/jwt';
import { LoginDto } from './dto/login.dto';
import * as bcrypt from 'bcrypt';
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwtService: JwtService
) {}
async login(loginDto: LoginDto) {
const usuario = await this.prisma.usuario.findUnique({
where: { email: loginDto.email }
});
if (!usuario) throw new UnauthorizedException('Credenciais Inválidas');
// Valida o Hash da Senha gravada no banco
const senhaValida = await bcrypt.compare(loginDto.senha, usuario.senha);
if (!senhaValida) throw new UnauthorizedException('Credenciais Inválidas');
// Prepara as informações a serem carimbadas no Payload do Token (O papel é essencial!)
const payload = { sub: usuario.id, email: usuario.email, role: usuario.role };
return {
usuario: { id: usuario.id, nome: usuario.nome, role: usuario.role },
token: this.jwtService.sign(payload)
};
}
}
(Nota: Crie um LoginDto na pasta /dto/ contendo email e senha com class-validator como fizemos no Módulo 03).
🛡️ 3. Estratégia JWT e Guards de Autorização
O Guard diz: “Você tem a credencial certa”. A Autorização diz: “A credencial é verdadeira, mas você NÃO tem permissão administrativa.”
A Estratégia de Descriptografia (jwt.strategy.ts)
Este arquivo é acionado pela biblioteca Passport toda vez que o @UseGuards(JwtAuthGuard) é chamado.
// src/auth/jwt.strategy.ts
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable } from '@nestjs/common';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: 'MINHA_CHAVE_SUPER_SECRETA_TECLOJA_2024',
});
}
async validate(payload: any) {
// O retorno deste método injeta os dados no objeto request.user do Express!
return { userId: payload.sub, email: payload.email, role: payload.role };
}
}
O Role Guard customizado (roles.guard.ts)
// src/auth/roles.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest();
const user = request.user; // Populado pelo JwtStrategy
if (!user || user.role !== 'ADMIN') {
throw new ForbiddenException('Acesso negado. Requer privilégios de Administrador.');
}
return true;
}
}
🛑 4. Trancando as Rotas Administrativas
Agora, voltaremos ao nosso ProdutoController para aplicar a segurança nas rotas de criação:
// src/produto/produto.controller.ts
import { Controller, Post, UseGuards /* imports... */ } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { RolesGuard } from '../auth/roles.guard';
@Controller('produtos')
export class ProdutoController {
@Post()
@UseGuards(AuthGuard('jwt'), RolesGuard) // Proteção Dupla!
create(@Body() createProdutoDto: CreateProdutoDto) {
return this.produtoService.create(createProdutoDto);
}
// O GET é público (qualquer usuário não-logado pode ver a vitrine)
@Get()
findAll() {
return this.produtoService.findAll();
}
}
✅ Pré-Requisitos deste Módulo
Antes de passar para o desenvolvimento do frontend SSR com Next.js no Módulo 06, certifique-se de que:
- As rotas de consulta e mutação de produtos e faturamentos foram implementadas no Módulo 04.
- As dependências de segurança e criptografia (
passport,passport-jwt,bcrypt) estão instaladas no repositório.
🤔 Por que fizemos assim?
- Por que validar senhas utilizando o algoritmo do
bcrypt.compare()? Gravar senhas em texto puro no banco de dados relacional é uma falha grave de segurança que expõe os usuários a vazamentos. O Bcrypt aplica um algoritmo de hash criptográfico unidirecional lento e seguro (com salting aleatório), tornando impossível descriptografar a senha de volta. Comparar as credenciais combcrypt.compare()apenas valida se a tentativa digitada gera o mesmo hash correspondente sem expor a senha em nenhum momento na memória física do servidor. - Por que emitir tokens de autenticação Stateless (JWT) em vez de gravar sessões no banco? Deploys corporativos escaláveis rodam em múltiplos containers na Render sob um load balancer. Se usássemos sessões stateful (gravadas na memória de uma das máquinas), a requisição seguinte do usuário poderia cair em outro container e acusar falha de login. O JWT carrega a identidade e privilégios do usuário em formato criptográfico assinado. O servidor NestJS apenas verifica a assinatura digital na requisição, permitindo validar e autorizar requisições de forma distribuída sem custos de consulta a sessões.
- Por que encadear
@UseGuards(AuthGuard('jwt'), RolesGuard)nessa ordem exata? O pipeline de segurança do NestJS processa os guards na sequência de declaração. OAuthGuard('jwt')(do Passport) roda primeiro, extraindo e descriptografando o Bearer token do header HTTP, injetando os dados decodificados no objeto de requisição (request.user). ORolesGuardroda em seguida, dependendo deste usuário injetado para ler seu papel (role) e compará-lo com as permissões exigidas pela rota administrativa, bloqueando acessos não autorizados de forma centralizada.
🔍 Checkpoint
- Geração de Token: Dispare uma requisição do tipo POST para
/auth/logininformando o e-mail semeadoadmin@tecloja.come a senhaadmin123. A resposta deve conter o objeto de usuário e a string do token JWT. - Proteção de Endpoint: Tente disparar um POST em
/produtossem passar cabeçalhos. A API deve rejeitar a requisição com o código de erro 401 Unauthorized. - Bloqueio de privilégios: Autentique-se com um usuário comum semeado (USER) e tente usar o token recebido no cabeçalho
Authorization: Bearer <token>para criar um produto. A API deve retornar 403 Forbidden sinalizando falta de privilégios.
⚠️ Erros Comuns
| Erro | Causa | Solução |
|---|---|---|
O objeto request.user chega como nulo ou indefinido dentro do RolesGuard |
Os guards foram declarados de forma invertida ou o AuthGuard não foi fornecido na rota administrativa. |
Certifique-se de declarar sempre o guard de autenticação antes do guard de privilégios na anotação: @UseGuards(AuthGuard('jwt'), RolesGuard). |
| Assinaturas JWT sendo rejeitadas como inválidas pelo validador | A chave secreta de criptografia declarada em AuthModule difere da secret configurada no construtor de JwtStrategy. |
Verifique detalhadamente se a string secreta (ou a variável de ambiente JWT_SECRET) bate exatamente nas duas classes configuradas. |
O build do Docker do backend falha ao compilar a dependência do bcrypt |
A imagem Alpine leve do Node não possui o compilador nativo (g++) necessário para buildar os binários nativos de C++ do bcrypt. | Instale a versão puramente JavaScript do Bcrypt (npm install bcryptjs e @types/bcryptjs para desenvolvimento) para evitar gargalos de compilação em containers Docker leves de produção. |
🏁 Conclusão (Fim da API Backend)
Parabéns! Nosso servidor NestJS está completo, seguro e performático. Ele está pronto para receber requisições de clientes Web, Mobile ou Desktop.
No Módulo 06, faremos a grande virada arquitetônica. Entraremos no repositório de Frontend (SPA/Vite) e substituí-lo-emos inteiramente pelo ecossistema majestoso do Next.js.