📚 Módulo 07: Frontend - Segurança com BFF e Route Handlers

No ecossistema React (SPA), a prática comum era enviar a senha para a API, receber o token JWT e salvá-lo no localStorage. Essa abordagem é vulnerável a ataques XSS (Cross-Site Scripting), pois qualquer script malicioso injetado na sua página consegue ler o seu token e roubar a sua conta.

No Next.js, temos um servidor à nossa disposição. Adotaremos o padrão BFF (Backend-For-Frontend) utilizando Route Handlers do Next.js e protegeremos nosso token blindando-o em um Cookie HttpOnly.


🛡️ 1. A Arquitetura do Login Seguro

Veja como o servidor intermediário atua para proteger o navegador de ter acesso direto ao token de segurança:

sequenceDiagram
    participant Browser as Cliente (Navegador)
    participant BFF as Next.js (Route Handler)
    participant Nest as API NestJS

    Browser->>BFF: POST /api/auth (Login + Senha)
    Note over BFF: O Next.js atua como BFF
    BFF->>Nest: POST /auth/login (Login + Senha)
    Nest-->>BFF: JSON com { Token JWT, User }
    
    Note over BFF: Next.js guarda o Token em Cookie HttpOnly
    BFF-->>Browser: Responde Sucesso + Set-Cookie HTTP
    Note over Browser: O Javascript do frontend não consegue ler o Cookie!

🔒 2. Criando a Rota do BFF (Route Handler)

No App Router do Next.js, a pasta src/app/api/ atua como um servidor backend real. Criaremos a rota interna que fará o meio-de-campo com o NestJS.

Instale a biblioteca de controle de cookies:

npm install cookies-next

Crie o arquivo do BFF em src/app/api/auth/login/route.ts:

import { NextResponse } from 'next/server';
import { cookies } from 'next/headers';

export async function POST(request: Request) {
  const body = await request.json();

  // 1. O Next.js (BFF) conversa com o NestJS (Backend Real)
  const res = await fetch('http://localhost:3000/auth/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });

  if (!res.ok) {
    return NextResponse.json({ erro: 'Credenciais inválidas' }, { status: 401 });
  }

  const dados = await res.json(); // { usuario: {...}, token: "ey..." }

  // 2. O Next.js empacota o Token em um Cookie Blindado!
  cookies().set({
    name: 'tecloja_token',
    value: dados.token,
    httpOnly: true, // PROÍBE o JavaScript do navegador de ler este cookie (Mitiga XSS)
    secure: process.env.NODE_ENV === 'production', // Apenas HTTPS em produção
    sameSite: 'lax', // Proteção contra ataques CSRF
    maxAge: 60 * 60 * 24, // 1 Dia
    path: '/',
  });

  // 3. Devolvemos para o React apenas os dados inofensivos do usuário
  return NextResponse.json({ usuario: dados.usuario });
}

💻 3. A Tela de Login (Client Component)

Agora, criaremos o formulário visual em React. Como ele tem inputs e gerencia estados, será um "use client".

Crie em src/app/login/page.tsx:

"use client";

import React, { useState } from 'react';
import { useRouter } from 'next/navigation';

export default function LoginPage() {
  const [email, setEmail] = useState('');
  const [senha, setSenha] = useState('');
  const [erro, setErro] = useState('');
  const router = useRouter();

  const handleLogin = async (e: React.FormEvent) => {
    e.preventDefault();
    
    // O navegador fala APENAS com a rota local do Next.js (/api/auth/login)
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, senha }),
    });

    if (res.ok) {
      // O Next.js já injetou o Cookie HttpOnly silenciosamente no navegador.
      // Agora redirecionamos o usuário para o painel de administração.
      router.push('/admin');
      router.refresh();
    } else {
      setErro('Email ou senha incorretos.');
    }
  };

  return (
    <div className="min-h-screen flex items-center justify-center bg-slate-900">
      <div className="bg-slate-800 p-8 rounded-xl shadow-2xl border border-slate-700 w-full max-w-md">
        <h2 className="text-2xl font-bold text-white mb-6 text-center">Login Administrativo</h2>
        
        {erro && <div className="bg-red-500/20 border border-red-500 text-red-400 p-3 rounded mb-4">{erro}</div>}
        
        <form onSubmit={handleLogin} className="flex flex-col gap-4">
          <div>
            <label className="text-slate-400 text-sm">E-mail</label>
            <input 
              type="email" 
              className="w-full mt-1 bg-slate-900 border border-slate-700 text-white rounded p-3 outline-none focus:border-blue-500"
              value={email}
              onChange={e => setEmail(e.target.value)}
              required
            />
          </div>
          <div>
            <label className="text-slate-400 text-sm">Senha</label>
            <input 
              type="password" 
              className="w-full mt-1 bg-slate-900 border border-slate-700 text-white rounded p-3 outline-none focus:border-blue-500"
              value={senha}
              onChange={e => setSenha(e.target.value)}
              required
            />
          </div>
          <button type="submit" className="mt-4 bg-blue-600 hover:bg-blue-500 text-white font-bold py-3 rounded transition-colors">
            Entrar
          </button>
        </form>
      </div>
    </div>
  );
}

🛑 4. Protegendo as Rotas de Administração (Middleware)

No React (Vite), protegíamos rotas com “Guards” de componentes no frontend. No Next.js, temos um mecanismo global e de altíssima performance: O Middleware que roda na Edge Network antes da página existir.

Crie o arquivo na raiz do projeto src/middleware.ts:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Pega a URL que o usuário está tentando acessar
  const { pathname } = request.nextUrl;

  // Se a rota for administrativa, vamos verificar o escudo
  if (pathname.startsWith('/admin')) {
    
    // Tenta ler o cookie HttpOnly que nós gravamos no momento do login
    const token = request.cookies.get('tecloja_token')?.value;

    if (!token) {
      // Se não tem token, bloqueia a passagem e joga pra tela de login
      return NextResponse.redirect(new URL('/login', request.url));
    }
  }

  // Se tudo estiver certo, libera a passagem
  return NextResponse.next();
}

// Configura o middleware para observar apenas rotas específicas
export const config = {
  matcher: ['/admin/:path*'],
};

✅ Pré-Requisitos deste Módulo

Antes de passar para o gerenciamento de estado global com Zustand e Server Actions no Módulo 08, certifique-se de que:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. Redirecionamento Ativo: Sem estar autenticado, tente forçar a URL do navegador direto para http://localhost:3000/admin. O middleware deve interceptar o acesso e redirecionar você instantaneamente para /login.
  2. Cookie HttpOnly Presente: Faça login com credenciais válidas. Abra a aba Inspecionador do Navegador -> Application -> Cookies. Verifique se a chave tecloja_token existe e se a coluna HTTPOnly possui um sinalizador ativo (indicando bloqueio de leitura JS).
  3. BFF em Rede: Valide no console de rede do navegador que a requisição de login foi enviada para o caminho local /api/auth/login e não diretamente para a API de NestJS.

⚠️ Erros Comuns

Erro Causa Solução
Loop infinito de redirecionamento do middleware para a rota /login O matcher de segurança do middleware está monitorando todas as rotas da aplicação, incluindo a própria tela de login. Certifique-se de configurar a propriedade matcher com a expressão exata ['/admin/:path*'] para limitar as checagens apenas a rotas administrativas.
O TypeScript acusa erro ao tentar ler cookies usando a API cookies() O desenvolvedor tentou utilizar o módulo de cabeçalhos e cookies do Next em um arquivo configurado como Client Component ("use client"). Cookies HttpOnly só podem ser manipulados do lado do servidor. Utilize chamadas HTTP do BFF ou Server Actions para interagir com dados protegidos no lado do cliente.
O login do BFF retorna erro de conexão rejeitada (Connection Refused) O endereço físico de chamada da API do NestJS no Route Handler está offline ou possui o número de porta incorreto. Verifique se a API do NestJS está rodando ativamente e aponte a chamada do fetch no route do login para a porta e domínio corretos (normalmente http://localhost:3000).

🏁 Conclusão

Sensacional! Subimos o nível da nossa segurança para um padrão corporativo irretocável. Usamos a API nativa do Next.js para criar um servidor intermediário (BFF) que blindou o token JWT e protegemos nossas páginas administrativas com Middleware.

No Módulo 08, completaremos a aplicação integrando o carrinho de compras com a biblioteca de estado moderno Zustand, e utilizaremos as inovadoras Server Actions para executar o faturamento do pedido.


Voltar para o Sumário