
Autenticação e Segurança: JWT vs. Server-Side Sessions no ecossistema Next.js + NestJS
Se existe um tema na engenharia de software capaz de gerar mais discussões do que a escolha entre espaços e tabs, é a autenticação.
Durante anos, a comunidade frontend adotou um padrão quase unânime: o usuário faz login, a API devolve um JSON Web Token (JWT), o frontend salva isso no localStorage e envia no header das próximas requisições. Simples, stateless e extremamente perigoso.
Com a maturidade do ecossistema e a ascensão de frameworks full-stack como o Next.js trabalhando em conjunto com backends robustos como o NestJS, a forma como arquitetamos a segurança mudou. O Next.js não é mais apenas um gerador de HTML; ele assumiu o papel de BFF (Backend-For-Frontend).
Neste artigo, vamos dissecar as abordagens de JWT e Server-Side Sessions, os riscos ocultos de cada uma, e como desenhar uma arquitetura de autenticação de nível de produção unindo Next.js e NestJS.
O Pecado Original: JWT no LocalStorage
Vamos tirar o elefante da sala. Salvar seu JWT de acesso no localStorage ou sessionStorage é expor sua aplicação a ataques de XSS (Cross-Site Scripting).
Se um único script malicioso for injetado no seu frontend (seja por uma dependência comprometida no NPM, uma falha de sanitização no React ou um script de analytics de terceiros), esse script tem acesso total ao localStorage. Ele pode roubar o token do seu usuário e se passar por ele até que o token expire.
A regra de ouro moderna é: O código JavaScript do cliente (o navegador) nunca deve ter acesso direto para ler o token de autenticação.
A Abordagem Padrão: JWT em Cookies HttpOnly
Para resolver o problema do XSS, a abordagem mais adotada hoje é armazenar o JWT em um cookie com as flags HttpOnly, Secure e SameSite=Strict.
A flag HttpOnly garante que o document.cookie do JavaScript não consiga ler o valor. O token viaja automaticamente nas requisições HTTP para o mesmo domínio.
O Fluxo Next.js + NestJS
Neste cenário, o Next.js age como um intermediário seguro:
- O cliente envia e-mail e senha para uma Server Action (ou Route Handler) no Next.js.
- O Next.js repassa essas credenciais para o NestJS.
- O NestJS valida no banco e retorna um JWT assinado para o Next.js.
- O Next.js pega esse JWT e "seta" um cookie
HttpOnlyno navegador do usuário.
// Next.js (Server Action de Login)
import { cookies } from 'next/headers';
export async function loginAction(formData: FormData) {
const response = await fetch('[https://api.seudominio.com/auth/login](https://api.seudominio.com/auth/login)', {
method: 'POST',
body: JSON.stringify(Object.fromEntries(formData)),
});
const { access_token } = await response.json();
// O JavaScript do browser NUNCA verá esse token
cookies().set('session_token', access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax', // ou 'strict' dependendo da sua arquitetura de subdomínios
maxAge: 60 * 60 * 24 * 7, // 1 semana
});
}
Nas próximas requisições para a API do NestJS, se o Next.js e o NestJS compartilharem o mesmo domínio raiz (ex: app.seudominio.com e api.seudominio.com), o cookie viaja automaticamente. Caso contrário, o Next.js (no servidor) lê o cookie e injeta o token no cabeçalho Authorization: Bearer antes de chamar o NestJS.
O Problema do JWT: A Invalidação
O JWT é incrível porque é stateless. O NestJS não precisa ir ao banco de dados para saber se o usuário está logado; ele apenas verifica a assinatura criptográfica usando o @nestjs/passport e um JwtAuthGuard. Isso escala maravilhosamente bem.
Mas essa é também a sua maior fraqueza. Como você desloga um usuário imediatamente? Se o usuário tiver a conta invadida, ou se um administrador banir aquele usuário, o JWT continuará sendo válido até expirar. Você não pode "apagar" um JWT que já foi emitido.
Se você começa a salvar uma blacklist de tokens revogados no Redis para checar em cada requisição, parabéns: você acabou de transformar sua arquitetura stateless em stateful, perdendo a principal vantagem do JWT.
Server-Side Sessions: O Retorno do Rei
É aqui que as Sessões do lado do servidor (Server-Side Sessions) voltam a brilhar, especialmente em produtos financeiros, SaaS B2B ou aplicações onde o controle de acesso precisa ser absoluto e imediato.
Em vez de colocar um JSON assinado com os dados do usuário no navegador, a API (NestJS) cria um registro de sessão em um banco de dados rápido (como o Redis) e devolve apenas um identificador opaco (um Session ID aleatório, como uma string UUID).
O Fluxo com Sessão (Redis)
- Next.js envia credenciais para o NestJS.
- NestJS valida, gera um
sessionId = 'abc-123', salva no Redis (key: abc-123,value: { userId: 1, role: 'admin' }) e retorna o ID. - Next.js guarda o
sessionIdnum cookieHttpOnly.
Para proteger rotas no NestJS, em vez de validar uma assinatura criptográfica, o Guard busca a sessão no Redis:
// NestJS (Exemplo de Guard para Sessões com Redis)
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { RedisService } from './redis.service';
@Injectable()
export class SessionGuard implements CanActivate {
constructor(private redis: RedisService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// O Next.js (BFF) extraiu o cookie e enviou via header para a API
const sessionId = request.headers['x-session-id'];
if (!sessionId) throw new UnauthorizedException();
const sessionData = await this.redis.get(`session:${sessionId}`);
if (!sessionData) throw new UnauthorizedException();
// Injeta o usuário no request para os controllers usarem
request.user = JSON.parse(sessionData);
return true;
}
}
Por que isso é poderoso?
- Controle Total: Quer forçar o logout de um usuário de todos os dispositivos? Basta deletar as chaves dele no Redis. No próximo clique, o Guard vai rejeitar a requisição.
- Payload Oculto: Nenhuma informação do usuário viaja no cookie. Com JWT, qualquer um que intercepte o token pode decodificar o base64 e ler os dados (embora não possa alterá-los sem quebrar a assinatura).
O Veredito Arquitetural: Qual escolher?
Como toda resposta na engenharia de software: depende do que você está construindo.
Escolha JWT (em Cookies HttpOnly) se:
- Você está construindo aplicações onde o dano de um token roubado com tempo de vida curto (ex: 15 minutos) é baixo.
- Você precisa de extrema escalabilidade e quer economizar chamadas ao banco de dados/Redis em cada requisição.
- Sua arquitetura envolve dezenas de microsserviços que precisam validar a identidade de forma descentralizada.
- Dica Prática: Implemente o padrão Refresh Token (também em cookie HttpOnly) para manter os JWTs de acesso com vida muito curta.
Escolha Server-Side Sessions (Redis) se:
- Você está construindo fintechs, sistemas de saúde, ou plataformas com rigorosas regras de compliance.
- Você precisa da capacidade de revogar acesso de forma instantânea (ex: botão "Desconectar de todos os outros dispositivos").
- Você precisa rastrear ativamente quantas sessões simultâneas um usuário tem abertas.
O Papel Fundamental do Next.js
Independente da escolha na sua API (NestJS), a arquitetura do Next.js com App Router simplificou drasticamente o gerenciamento de credenciais no frontend.
Ao usar os React Server Components e as Server Actions, você abstrai a complexidade do navegador. O cliente não precisa conhecer JWTs, bibliotecas de interceptors do Axios ou lógicas complexas de Refresh Token. O navegador apenas lida com um cookie invisível, enquanto o servidor Node do Next.js faz o trabalho pesado de dialogar com sua infraestrutura backend.
A segurança da sua aplicação não é apenas sobre o algoritmo de criptografia que você escolhe, mas sobre a superfície de ataque que você expõe. E no frontend moderno, quanto menos o JavaScript do cliente souber, mais seguro seu usuário estará.
Comentários
Carregando comentários...
Participe da conversa
Entre com sua conta para comentar neste artigo.