🚀 Oferta especial: 60% OFF no CrazyStack - Últimas vagas!Garantir vaga →
Voltar ao Curso
MÓDULO 2
AULA 4

Sistema de Autenticação

Implementação completa de autenticação com email/senha, CSRF tokens, cookies seguros e integração com a API do Restaurantix.

60 min
CSRF Protection
Cookies Seguros
🔐 Autenticação com Cookies no Restaurantix

🎯 Por que Cookies ao invés de JWT no localStorage?

  • Segurança XSS: Cookies HttpOnly não podem ser acessados via JavaScript
  • Automático: Browser envia cookies automaticamente em cada requisição
  • CSRF Protection: Usamos tokens CSRF para validação adicional
  • Server-side: Controle total do servidor sobre autenticação

O Restaurantix implementa autenticação tradicional com email/senha, usando cookies seguros para JWT tokens e proteção CSRF. Esta abordagem é mais segura que armazenar tokens no localStorage, especialmente contra ataques XSS.

📋 Fluxo de Autenticação Completo

1. CSRF Token:

Cliente solicita token CSRF do servidor antes de qualquer operação sensível

2. Login:

Usuário envia email/senha + CSRF token no header da requisição

3. JWT Cookie:

Servidor valida credenciais e define cookie httpOnly com JWT

4. Requests:

Browser envia cookie automaticamente em requisições subsequentes

5. Validação:

Middleware do servidor valida JWT e autoriza acesso

🛡️ Camadas de Segurança

HttpOnly:

Cookies inacessíveis via JavaScript, protege contra XSS

CSRF Protection:

Tokens únicos em headers previnem ataques Cross-Site

SameSite:

Política que bloqueia cookies de sites externos

Secure Flag:

Cookies só enviados via HTTPS em produção

Expiration:

Tokens têm tempo limitado de vida

⚠️ Conceitos Importantes para Entender

CSRF (Cross-Site Request Forgery):

Ataque onde um site malicioso força seu browser a fazer requisições em outro site onde você está logado.

XSS (Cross-Site Scripting):

Ataque onde código JavaScript malicioso é injetado e executado no seu browser.

HttpOnly Cookie:

Cookie que não pode ser acessado via JavaScript, apenas pelo servidor.

SameSite Policy:

Política que controla quando cookies são enviados em requisições cross-site.

Passo 1: Cliente da API com Cookies e CSRF

🎯 Objetivo desta Seção

Vamos criar uma estrutura robusta para comunicação com a API do backend, implementando: validação de variáveis de ambiente, route handlers no Next.js e cliente HTTP com proteção CSRF.

📁 src/lib/env.ts - Validação Segura de Variáveis

💡 Por que validar variáveis de ambiente?

  • Falha rápida: Aplicação para se variáveis estão ausentes
  • TypeScript safety: Garantia de tipos nas variáveis
  • Documentação: Código autodocumentado sobre dependências
  • Debugging: Erros claros quando configuração está incorreta
// 📁 Validação de variáveis de ambiente com Zod
import { z } from 'zod';

// 🔧 Schema de validação - garante que todas as env vars estão presentes e válidas
const envSchema = z.object({
  NEXT_PUBLIC_API_URL: z.string().url(), // ✅ Valida se é uma URL válida
  NEXT_PUBLIC_APP_NAME: z.string().min(1), // ✅ Não pode ser string vazia
  NEXT_PUBLIC_APP_VERSION: z.string().min(1), // ✅ Versão obrigatória
});

// 🚀 Parse das variáveis - falha na inicialização se algo estiver errado
export const env = envSchema.parse({
  NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
  NEXT_PUBLIC_APP_NAME: process.env.NEXT_PUBLIC_APP_NAME,
  NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION,
});

// 💡 Uso seguro em qualquer lugar da aplicação:
// import { env } from '@/lib/env'
// const apiUrl = env.NEXT_PUBLIC_API_URL // TypeScript sabe que é string válida

// 🔍 Exemplo de arquivo .env.local:
// NEXT_PUBLIC_API_URL=http://localhost:8080
// NEXT_PUBLIC_APP_NAME=Restaurantix
// NEXT_PUBLIC_APP_VERSION=1.0.0

🛡️ Route Handlers - Proxy Seguro para o Backend

🤔 Por que usar Route Handlers como proxy?

  • CORS simplificado: Evita problemas de Cross-Origin requests
  • Cookies seguros: Servidor pode definir HttpOnly cookies
  • Middleware centralizado: Validação e logs em um lugar
  • Abstração: Frontend não precisa saber detalhes do backend

🔐 CSRF Token Route Handler

import { NextResponse } from 'next/server';
import { env } from '@/lib/env';

export async function GET() {
  try {
    // 🔗 Fazemos proxy da requisição para o backend
    const response = await fetch(`${env.NEXT_PUBLIC_API_URL}/csrf-token`, {
      method: 'GET',
      headers: {
        Accept: 'application/json',
      },
      credentials: 'include', // ✅ Importante: inclui cookies na requisição
    });

    const data = await response.json();

    if (!response.ok) {
      return NextResponse.json(
        { error: data.message || 'Erro ao obter CSRF token' },
        { status: response.status },
      );
    }

    // 📦 Criamos a resposta para o cliente
    const nextResponse = NextResponse.json(data);

    // 🍪 CRUCIAL: Propagamos os cookies do backend para o cliente
    const setCookieHeader = response.headers.get('set-cookie');
    if (setCookieHeader) {
      // O Next.js espera múltiplos Set-Cookie headers como um array
      const cookies = setCookieHeader.split(', ');
      cookies.forEach((cookie) => {
        nextResponse.headers.append('Set-Cookie', cookie);
      });
    }

    return nextResponse;
  } catch (error) {
    if (process.env.NODE_ENV === 'development') {
      console.error('🚨 Erro no route handler de csrf-token:', error);
    }
    return NextResponse.json({ error: 'Erro ao processar requisição' }, { status: 500 });
  }
}

// 🎯 Este route handler será acessível em /api/csrf-token
// e funcionará como um proxy seguro para o backend

👤 Route Handler de Cadastro (Sign-up)

import { NextRequest, NextResponse } from 'next/server';
import { env } from '@/lib/env';

export async function POST(request: NextRequest) {
  try {
    // 📥 Pegamos os dados do corpo da requisição
    const body = await request.json();

    // 🔗 Fazemos proxy para o endpoint de cadastro do backend
    const response = await fetch(`${env.NEXT_PUBLIC_API_URL}/auth/sign-up`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: JSON.stringify(body), // 📤 Repassamos os dados recebidos
      credentials: 'include', // ✅ Inclui cookies (importante para CSRF)
    });

    const data = await response.json();

    if (!response.ok) {
      return NextResponse.json(
        { error: data.message || 'Erro ao criar conta' },
        { status: response.status },
      );
    }

    // 📦 Criamos resposta de sucesso
    const nextResponse = NextResponse.json(data);

    // 🍪 CRUCIAL: Propagamos cookies de autenticação do backend
    const setCookieHeader = response.headers.get('set-cookie');
    if (setCookieHeader) {
      // Cookies podem conter: auth token, csrf token, session info
      const cookies = setCookieHeader.split(', ');
      cookies.forEach((cookie) => {
        nextResponse.headers.append('Set-Cookie', cookie);
      });
    }

    return nextResponse;
  } catch (error) {
    if (process.env.NODE_ENV === 'development') {
      console.error('🚨 Erro no route handler de sign-up:', error);
    }
    return NextResponse.json({ error: 'Erro ao processar requisição' }, { status: 500 });
  }
}

// 📝 Dados esperados no body:
// {
//   "name": "João Silva",
//   "email": "joao@email.com", 
//   "password": "senha123",
//   "role": "customer" | "manager"
// }

🔑 Route Handler de Login (Sign-in)

import { NextRequest, NextResponse } from 'next/server';
import { env } from '@/lib/env';

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

    // 🔗 Fazemos proxy para o endpoint de login do backend
    const response = await fetch(`${env.NEXT_PUBLIC_API_URL}/auth/sign-in`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
      },
      body: JSON.stringify(body),
      credentials: 'include', // ✅ Para enviar CSRF token via cookies
    });

    const data = await response.json();

    if (!response.ok) {
      return NextResponse.json(
        { error: data.message || 'Erro ao fazer login' },
        { status: response.status },
      );
    }

    // 📦 Resposta de sucesso
    const nextResponse = NextResponse.json(data);

    // 🍪 Propagamos o cookie de autenticação JWT
    const setCookieHeader = response.headers.get('set-cookie');
    if (setCookieHeader) {
      const cookies = setCookieHeader.split(', ');
      cookies.forEach((cookie) => {
        nextResponse.headers.append('Set-Cookie', cookie);
      });
    }

    return nextResponse;
  } catch (error) {
    if (process.env.NODE_ENV === 'development') {
      console.error('🚨 Erro no route handler de sign-in:', error);
    }
    return NextResponse.json({ error: 'Erro ao processar requisição' }, { status: 500 });
  }
}

// 📝 Dados esperados no body:
// {
//   "email": "joao@email.com",
//   "password": "senha123"
// }

👤 Route Handler de Perfil (/me)

import { NextRequest, NextResponse } from 'next/server';
import { env } from '@/lib/env';

export async function GET(request: NextRequest) {
  try {
    // 🍪 Pegamos os cookies da requisição atual (incluindo JWT)
    const cookieHeader = request.headers.get('cookie') || '';

    // 🔗 Fazemos requisição para o backend incluindo os cookies
    const response = await fetch(`${env.NEXT_PUBLIC_API_URL}/me`, {
      method: 'GET',
      headers: {
        Cookie: cookieHeader, // 📤 Repassamos todos os cookies
        Accept: 'application/json',
      },
    });

    if (!response.ok) {
      return NextResponse.json(
        { error: 'Usuário não autenticado' }, 
        { status: response.status }
      );
    }

    const data = await response.json();

    // 📦 Resposta com dados do usuário
    const nextResponse = NextResponse.json(data);

    // 🍪 Propagamos novos cookies se houver (refresh de tokens, etc.)
    const setCookieHeaders = response.headers.get('set-cookie');
    if (setCookieHeaders) {
      const cookies = setCookieHeaders.split(', ');
      cookies.forEach((cookie) => {
        nextResponse.headers.append('Set-Cookie', cookie);
      });
    }

    return nextResponse;
  } catch (error) {
    if (process.env.NODE_ENV === 'development') {
      console.error('🚨 Erro no route handler de perfil:', error);
    }
    return NextResponse.json({ error: 'Erro ao processar requisição' }, { status: 500 });
  }
}

// 📋 Resposta esperada:
// {
//   "id": "user-123",
//   "name": "João Silva",
//   "email": "joao@email.com",
//   "role": "customer"
// }

🚀 src/lib/api.ts - Cliente HTTP Inteligente

🧠 O que torna este cliente "inteligente"?

  • CSRF automático: Obtém e inclui tokens CSRF quando necessário
  • Retry logic: Tenta novamente com novo token se CSRF expirar
  • Roteamento inteligente: Usa route handlers para auth, API direta para dados
  • Error handling: Tratamento centralizado de erros
src/lib/api.ts
 /* eslint-disable no-console */
import { env } from './env';

class ApiClient {
  private baseURL: string;
  private csrfToken: string | null = null; // 🔐 Cache do token CSRF

  constructor() {
    this.baseURL = env.NEXT_PUBLIC_API_URL;
  }

  // ✅ Buscar token CSRF do servidor via Route Handler local
  async getCsrfToken(): Promise<string> {
    try {
      console.log('🔐 Tentando obter token CSRF via Route Handler local');

      // 🎯 Usamos nosso route handler como proxy
      const response = await fetch('/api/csrf-token', {
        method: 'GET',
        credentials: 'include', // ✅ Essencial para cookies
        headers: {
          Accept: 'application/json',
          'Cache-Control': 'no-cache', // 🚫 Sempre buscar token fresco
          Pragma: 'no-cache',
        },
        mode: 'cors',
        cache: 'no-store', // 🚫 Não cachear resposta
      });

      if (!response.ok) {
        const errorText = await response.text();
        console.error('🔐 Erro na resposta CSRF:', errorText);
        throw new Error(`Erro HTTP ${response.status}: ${response.statusText}`);
      }

      const data = await response.json();

      if (!data.token) {
        throw new Error('Token CSRF não recebido do servidor');
      }

      this.csrfToken = data.token; // 💾 Salvamos no cache
      return data.token;
    } catch (error) {
      console.error('🔐 Erro ao obter token CSRF:', error);
      throw new Error('Erro ao obter token CSRF');
    }
  }

  // ✅ Método privado para requisições com lógica inteligente
  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    // 🎯 Roteamento inteligente: auth via route handlers, dados via API direta
    const authRoutes = ['/auth/sign-in', '/auth/sign-up', '/csrf-token', '/me'];
    const isAuthRoute = authRoutes.some((route) => endpoint.includes(route));

    // 🔀 Decisão de rota: local para auth, backend direto para dados
    const url = isAuthRoute ? `/api${endpoint}` : `${this.baseURL}${endpoint}`;

    // 🛡️ Verificação de métodos que precisam de CSRF
    const unsafeMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
    const method = (options.method || 'GET').toUpperCase();

    // ✅ Para métodos não seguros em rotas não-auth, garantir CSRF token
    if (unsafeMethods.includes(method) && !isAuthRoute) {
      const isLoginEndpoint = endpoint === '/auth/sign-in';

      if (!isLoginEndpoint || this.csrfToken) {
        try {
          await this.getCsrfToken();
        } catch (error) {
          console.warn('⚠️ Falha ao obter token CSRF:', error);
          // Para login inicial, continuar sem CSRF
        }
      }
    }

    // 🔧 Configuração da requisição
    const config: RequestInit = {
      credentials: 'include', // ✅ Sempre incluir cookies
      mode: 'cors', // ✅ Permitir CORS para desenvolvimento
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json',
        // ✅ Headers para compatibilidade com CSRF strict
        'Cache-Control': 'no-cache',
        Pragma: 'no-cache',
        // ✅ Incluir CSRF token para métodos não seguros
        ...(this.csrfToken &&
          unsafeMethods.includes(method) &&
          !isAuthRoute && {
            'X-CSRF-Token': this.csrfToken,
          }),
        ...options.headers,
      },
      ...options,
    };

    try {
      const response = await fetch(url, config);

      // ✅ Tratamento inteligente de erros
      if (!response.ok) {
        let errorMessage = 'Erro na requisição';

        try {
          const errorData = await response.json();
          errorMessage = errorData.message || errorData.error || errorMessage;
        } catch {
          errorMessage = `Erro HTTP ${response.status}: ${response.statusText}`;
        }

        // 🔄 Se erro de CSRF, limpar token e tentar novamente (retry logic)
        if (response.status === 403 && errorMessage.includes('CSRF')) {
          this.csrfToken = null; // 🗑️ Limpar token inválido
          console.warn('⚠️ Token CSRF inválido, tentando novamente...');

          // 🔄 Tentar uma vez mais com novo token
          if (unsafeMethods.includes(method) && !isAuthRoute) {
            try {
              await this.getCsrfToken();
              config.headers = {
                ...config.headers,
                'X-CSRF-Token': this.csrfToken!,
              };

              const retryResponse = await fetch(url, config);
              if (retryResponse.ok) {
                return retryResponse.json();
              }
            } catch {
              // Se falhar novamente, deixar o erro original ser lançado
            }
          }
        }

        throw new Error(errorMessage);
      }

      // ✅ Verificar se a resposta tem conteúdo JSON
      const contentType = response.headers.get('content-type');
      if (contentType && contentType.includes('application/json')) {
        return response.json();
      }

      // Se não for JSON, retornar resposta vazia
      return {} as T;
    } catch (error) {
      console.error(`🚨 Erro na requisição para ${endpoint}:`, error);
      throw error;
    }
  }

  // 🎯 Métodos públicos da API (interface limpa para o frontend)
  async signUp(data: {
    name: string;
    email: string;
    password: string;
    role?: 'manager' | 'customer';
  }) {
    return this.request('/auth/sign-up', {
      method: 'POST',
      body: JSON.stringify(data),
    });
  }

  async signIn(email: string, password: string) {
    return this.request('/auth/sign-in', {
      method: 'POST',
      body: JSON.stringify({ email, password }),
    });
  }

  async getProfile() {
    return this.request('/me');
  }

  async signOut() {
    return this.request('/sign-out', { method: 'GET' });
  }

  // 🎯 Métodos para funcionalidades do app
  async getOrders(params?: Record<string, string>) {
    const searchParams = params ? new URLSearchParams(params) : '';
    return this.request(`/orders?${searchParams}`);
  }

  async getMetrics() {
    return this.request('/metrics');
  }

  // 🔐 Redefinição de senha
  async requestPasswordReset(email: string) {
    return this.request('/auth/request-password-reset', {
      method: 'POST',
      body: JSON.stringify({ email }),
    });
  }

  async resetPassword(token: string, newPassword: string) {
    return this.request('/auth/reset-password', {
      method: 'POST',
      body: JSON.stringify({ token, newPassword }),
    });
  }
}

// 🚀 Exportamos uma instância única (singleton pattern)
export const api = new ApiClient();

// 💡 Uso em componentes:
// import { api } from '@/lib/api'
// const user = await api.getProfile()

🔑 Resumo dos Conceitos Implementados

  • ✅ Validação de ENV: Falha rápida se configuração incorreta
  • ✅ Route Handlers: Proxy seguro evitando CORS
  • ✅ Cookie Propagation: Repassa cookies do backend
  • ✅ CSRF Protection: Tokens automáticos em requisições
  • ✅ Retry Logic: Tenta novamente se CSRF expirar
  • ✅ Error Handling: Tratamento centralizado de erros
  • ✅ Roteamento Inteligente: Auth local, dados diretos
  • ✅ TypeScript Safety: Tipos seguros em toda API
Passo 2: Store de Autenticação (Zustand)

🎯 Por que Zustand para Gerenciamento de Estado?

  • Simplicidade: API mais simples que Redux
  • Sem boilerplate: Menos código redundante
  • TypeScript nativo: Excelente suporte a tipos
  • Persistência fácil: Middleware built-in
  • Performance: Re-renders otimizados
  • DevTools: Integração com React DevTools
  • Flexibilidade: Funciona fora do React
  • Bundle size: Muito menor que Redux

📦 Instalação das Dependências

🔧 Pacotes Necessários:

  • zustand: Biblioteca principal para gerenciamento de estado
  • @types/js-cookie: Tipos TypeScript para manipulação de cookies
  • js-cookie: Utilitários para trabalhar com cookies (opcional para debug)
install-zustand.sh
npm install zustand
npm install @types/js-cookie js-cookie

🏪 src/store/auth-store.ts - Store de Autenticação Completo

🧠 Conceitos Implementados no Store:

  • Estado centralizado: Dados do usuário em um local
  • Persistência automática: Mantém estado entre reloads
  • Actions assíncronas: Integração com API
  • Error handling: Tratamento de erros centralizado
src/store/auth-store.ts
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable no-unused-vars */
'use client';

import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { api } from '@/lib/api';

// 📝 Tipagem do usuário - define estrutura dos dados
interface User {
  id: string;
  name: string;
  email: string;
  role: 'manager' | 'customer';
}

// 📝 Tipagem dos dados de cadastro
interface SignUpData {
  name: string;
  email: string;
  password: string;
  role?: 'manager' | 'customer'; // Opcional, padrão: customer
}

// 🏪 Interface do Store - define todo o estado e ações disponíveis
interface AuthState {
  // 📊 Estado (State)
  user: User | null;                // Dados do usuário logado ou null
  isLoading: boolean;               // Loading state para UX
  isAuthenticated: boolean;         // Flag derivada do user

  // ⚡ Ações (Actions) - métodos para modificar o estado
  signIn: (email: string, password: string) => Promise<void>;
  signUp: (data: SignUpData) => Promise<void>;
  signOut: () => Promise<void>;
  refreshUser: () => Promise<void>;
  getProfile: () => Promise<void>;
  setLoading: (loading: boolean) => void;
  setUser: (user: User | null) => void;
}

// 🏗️ Criação do Store com persist middleware
export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      // 🌟 Estado inicial
      user: null,
      isLoading: true,         // Começa como true para verificar auth inicial
      isAuthenticated: false,

      // 🔧 Ações básicas de estado
      setLoading: (loading: boolean) => {
        set({ isLoading: loading });
      },

      setUser: (user: User | null) => {
        set({
          user,
          isAuthenticated: !!user, // Converte user para boolean
        });
      },

      // 🔑 Ação de Login
      signIn: async (email: string, password: string) => {
        try {
          set({ isLoading: true });

          // 🌐 Chama API de login
          const response = await api.signIn(email, password);

          // 🎯 Estratégia: se resposta tem dados do usuário, usar diretamente
          if (response && typeof response === 'object' && 'id' in response) {
            const user = response as User;
            set({
              user,
              isAuthenticated: true,
              isLoading: false,
            });
          } else {
            // 🔄 Fallback: buscar perfil após login bem-sucedido
            try {
              const userData = (await api.getProfile()) as User;
              set({
                user: userData,
                isAuthenticated: true,
                isLoading: false,
              });
            } catch (profileError) {
              // 🚨 Último recurso: usuário temporário
              const tempUser = { 
                id: 'temp', 
                name: 'Usuário', 
                email, 
                role: 'customer' as const 
              };
              set({
                user: tempUser,
                isAuthenticated: true,
                isLoading: false,
              });
            }
          }
        } catch (error) {
          set({ isLoading: false });
          throw new Error('Email ou senha incorretos');
        }
      },

      // 👤 Ação de Cadastro
      signUp: async (data: SignUpData) => {
        try {
          set({ isLoading: true });

          // 🌐 Chama API de cadastro
          await api.signUp(data);

          // 👥 Após cadastro, usuário normalmente já está logado
          const userData = (await api.getProfile()) as User;
          if (!userData) {
            throw new Error('Usuário não encontrado após registro');
          }

          set({
            user: userData,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error) {
          set({ isLoading: false });
          if (process.env.NODE_ENV === 'development') {
            console.error('🚨 Erro ao criar conta:', error);
          }
          throw new Error('Erro ao criar conta. Email pode já estar em uso.');
        }
      },

      // 🚪 Ação de Logout
      signOut: async () => {
        try {
          // 🌐 Tenta fazer logout no servidor
          await api.signOut();
        } catch (error) {
          // ⚠️ Ignorar erros de logout - cookies podem já ter expirado
          if (process.env.NODE_ENV === 'development') {
            console.warn('⚠️ Erro no logout:', error);
          }
        } finally {
          // 🧹 Sempre limpar estado local, independente do resultado
          set({
            user: null,
            isAuthenticated: false,
            isLoading: false,
          });
        }
      },

      // 🔄 Ação para atualizar dados do usuário
      refreshUser: async () => {
        try {
          set({ isLoading: true });
          const userData = (await api.getProfile()) as User;
          set({
            user: userData,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error) {
          if (process.env.NODE_ENV === 'development') {
            console.error('🚨 Erro ao atualizar dados do usuário:', error);
          }
          // 🚨 Se falhar, usuário não está mais autenticado
          set({
            user: null,
            isAuthenticated: false,
            isLoading: false,
          });
        }
      },

      // 👤 Ação para buscar perfil (similar ao refresh, mas com error handling diferente)
      getProfile: async () => {
        try {
          set({ isLoading: true });
          const userData = (await api.getProfile()) as User;
          set({
            user: userData,
            isAuthenticated: true,
            isLoading: false,
          });
        } catch (error) {
          if (process.env.NODE_ENV === 'development') {
            console.error('🚨 Erro ao buscar perfil:', error);
          }
          set({
            user: null,
            isAuthenticated: false,
            isLoading: false,
          });
          throw error; // Re-throw para o componente lidar
        }
      },
    }),
    {
      // ⚙️ Configuração do middleware de persistência
      name: 'auth-storage',          // Nome da chave no localStorage
      partialize: (state) => ({      // Quais partes do estado persistir
        user: state.user,
        isAuthenticated: state.isAuthenticated,
        // ❌ NÃO persistimos isLoading - sempre inicia como true
      }),
    },
  ),
);

// 💡 Exemplos de uso nos componentes:

// 📖 Ler estado:
// const { user, isAuthenticated, isLoading } = useAuthStore();

// 🎯 Usar seletores para performance:
// const user = useAuthStore((state) => state.user);
// const signIn = useAuthStore((state) => state.signIn);

// 🏃 Usar shallow para objetos:
// import { useShallow } from 'zustand/react/shallow';
// const { user, isAuthenticated } = useAuthStore(
//   useShallow((state) => ({
//     user: state.user,
//     isAuthenticated: state.isAuthenticated,
//   }))
// );

🔍 Conceitos Avançados Implementados

Persist Middleware:

Automaticamente salva e carrega estado do localStorage. Útil para manter usuário logado entre sessões.

Partialize:

Escolhe quais partes do estado persistir. Não persistimos isLoading para sempre começar verificando autenticação.

Error Boundaries:

Todas as ações tratam erros graciosamente, mantendo a UI estável mesmo com falhas de rede.

Estado Derivado:

isAuthenticated é derivado de user (!!user), evitando inconsistências de estado.

🎯 Padrões de Uso nos Componentes

📖 Leitura de Estado:

// ✅ Leitura simples
const { user, isAuthenticated, isLoading } = useAuthStore();

// ✅ Seletor específico (melhor performance)
const user = useAuthStore((state) => state.user);
const signIn = useAuthStore((state) => state.signIn);

// ✅ Múltiplos valores com shallow (evita re-renders desnecessários)
import { useShallow } from 'zustand/react/shallow';
const { user, isAuthenticated } = useAuthStore(
  useShallow((state) => ({
    user: state.user,
    isAuthenticated: state.isAuthenticated,
  }))
);

⚡ Ações Assíncronas:

// ✅ Login com error handling
const signIn = useAuthStore((state) => state.signIn);

const handleLogin = async (email: string, password: string) => {
  try {
    await signIn(email, password);
    router.push('/dashboard');
    toast.success('Login realizado com sucesso!');
  } catch (error) {
    toast.error('Erro ao fazer login');
  }
};

// ✅ Logout simples
const signOut = useAuthStore((state) => state.signOut);
const handleLogout = () => {
  signOut(); // Sempre funciona, mesmo com erro de rede
  router.push('/');
};

✅ Vantagens desta Implementação

  • 🔒 Seguro: Estado sempre consistente com servidor
  • ⚡ Performático: Seletores evitam re-renders desnecessários
  • 🔄 Resiliente: Funciona mesmo com falhas de rede
  • 💾 Persistente: Mantém login entre sessões
  • 🧪 Testável: Lógica isolada e fácil de testar
  • 🔧 Manutenível: Código claro e bem documentado
  • 📱 Universal: Funciona em web e mobile
  • 🎯 TypeSafe: Tipos garantem integridade dos dados
Passo 3: Formulários com React Hook Form + Zod + Server Actions

🎯 A Tríade Perfeita: React Hook Form + Zod + Server Actions

Esta combinação oferece uma experiência completa de formulários: performance otimizada, validação robusta e server-side security.

React Hook Form

Performance com uncontrolled inputs e re-renders mínimos

Zod Validation

Schema-first validation com TypeScript safety

Server Actions

Segurança server-side e progressiveenhancement

📁 src/lib/auth-validations.ts - Schemas de Validação

🧠 Por que Zod para Validação?

  • Schema-first: Define validação uma vez, usa em client e server
  • TypeScript inference: Tipos gerados automaticamente
  • Composição: Reutiliza schemas menores em maiores
  • Mensagens customizadas: Feedback preciso para usuários
src/lib/auth-validations.ts
import { z } from 'zod';

// 🔧 Schema base para email (reutilizável)
const emailSchema = z
  .string()
  .min(1, 'Email é obrigatório')
  .email('Email inválido')
  .max(100, 'Email muito longo');

// 🔒 Schema base para senha (reutilizável)  
const passwordSchema = z
  .string()
  .min(1, 'Senha é obrigatória')
  .min(6, 'Senha deve ter pelo menos 6 caracteres')
  .max(100, 'Senha muito longa');

// 👤 Schema para cadastro
export const signUpSchema = z.object({
  name: z
    .string()
    .min(1, 'Nome é obrigatório')
    .min(2, 'Nome deve ter pelo menos 2 caracteres')
    .max(50, 'Nome muito longo')
    .regex(/^[a-zA-ZÀ-ÿs]+$/, 'Nome deve conter apenas letras'),
  
  email: emailSchema, // ♻️ Reutilizamos o schema base
  
  password: passwordSchema, // ♻️ Reutilizamos o schema base
  
  confirmPassword: z.string().min(1, 'Confirmação de senha é obrigatória'),
  
  role: z.enum(['customer', 'manager']).default('customer'),
}).refine((data) => data.password === data.confirmPassword, {
  // 🔍 Validação customizada para senhas iguais
  message: 'As senhas não coincidem',
  path: ['confirmPassword'], // Campo onde o erro será mostrado
});

// 🔑 Schema para login (mais simples)
export const signInSchema = z.object({
  email: emailSchema,     // ♻️ Reutilizamos
  password: passwordSchema, // ♻️ Reutilizamos
});

// 📧 Schema para reset de senha
export const requestPasswordResetSchema = z.object({
  email: emailSchema, // ♻️ Reutilizamos
});

// 🔄 Schema para nova senha
export const resetPasswordSchema = z.object({
  token: z.string().min(1, 'Token inválido'),
  password: passwordSchema, // ♻️ Reutilizamos
  confirmPassword: z.string().min(1, 'Confirmação de senha é obrigatória'),
}).refine((data) => data.password === data.confirmPassword, {
  message: 'As senhas não coincidem',
  path: ['confirmPassword'],
});

// 🎯 Tipos TypeScript gerados automaticamente pelos schemas
export type SignUpFormData = z.infer<typeof signUpSchema>;
export type SignInFormData = z.infer<typeof signInSchema>;
export type RequestPasswordResetFormData = z.infer<typeof requestPasswordResetSchema>;
export type ResetPasswordFormData = z.infer<typeof resetPasswordSchema>;

// 💡 Exemplo de uso dos tipos:
// function handleSignUp(data: SignUpFormData) {
//   // TypeScript sabe que data tem: name, email, password, confirmPassword, role
//   console.log(data.name); // ✅ string
//   console.log(data.email); // ✅ string (validado como email)
//   console.log(data.role); // ✅ 'customer' | 'manager'
// }

⚡ src/lib/auth-actions.ts - Server Actions Seguras

🛡️ Por que Server Actions são Mais Seguras?

  • Server-side validation: Validação nunca pode ser bypassada
  • No API exposure: Não expõe endpoints públicos
  • CSRF protection: Built-in no Next.js
  • Rate limiting: Mais fácil implementar throttling
src/lib/auth-actions.ts
'use server';

import { redirect } from 'next/navigation';
import { api } from '@/lib/api';
import {
  signUpSchema,
  signInSchema,
  requestPasswordResetSchema,
  type SignUpFormData,
  type SignInFormData,
  type RequestPasswordResetFormData,
} from '@/lib/auth-validations';

// 🎯 Tipo de retorno padronizado para Server Actions
interface ActionResult {
  success: boolean;
  error?: string;
  data?: any;
}

// 👤 Server Action para Cadastro
export async function signUpAction(formData: FormData): Promise<ActionResult> {
  try {
    // 📥 Extrair dados do FormData
    const rawData = {
      name: formData.get('name') as string,
      email: formData.get('email') as string,
      password: formData.get('password') as string,
      confirmPassword: formData.get('confirmPassword') as string,
      role: (formData.get('role') as 'customer' | 'manager') || 'customer',
    };

    // 🔍 Validação server-side (CRUCIAL - nunca confie apenas no cliente)
    const validationResult = signUpSchema.safeParse(rawData);
    
    if (!validationResult.success) {
      // 📝 Formatar erros de validação para UI
      const errorMessages = validationResult.error.errors
        .map((err) => `${err.path.join('.')}: ${err.message}`)
        .join(', ');
      
      return {
        success: false,
        error: errorMessages,
      };
    }

    const validData = validationResult.data;

    // 🌐 Tentar criar conta via API
    await api.signUp({
      name: validData.name,
      email: validData.email,
      password: validData.password,
      role: validData.role,
    });

    // ✅ Sucesso - redirecionar para dashboard
    redirect('/dashboard');
    
  } catch (error) {
    console.error('🚨 Erro no cadastro:', error);
    
    // 🎯 Tratamento de erros específicos
    if (error instanceof Error) {
      if (error.message.includes('email')) {
        return {
          success: false,
          error: 'Este email já está em uso. Tente fazer login.',
        };
      }
      
      return {
        success: false,
        error: error.message,
      };
    }

    return {
      success: false,
      error: 'Erro interno do servidor. Tente novamente.',
    };
  }
}

// 🔑 Server Action para Login
export async function signInAction(formData: FormData): Promise<ActionResult> {
  try {
    // 📥 Extrair dados do FormData
    const rawData = {
      email: formData.get('email') as string,
      password: formData.get('password') as string,
    };

    // 🔍 Validação server-side
    const validationResult = signInSchema.safeParse(rawData);
    
    if (!validationResult.success) {
      const errorMessages = validationResult.error.errors
        .map((err) => `${err.path.join('.')}: ${err.message}`)
        .join(', ');
      
      return {
        success: false,
        error: errorMessages,
      };
    }

    const { email, password } = validationResult.data;

    // 🌐 Tentar fazer login via API
    await api.signIn(email, password);

    // ✅ Sucesso - redirecionar para dashboard
    redirect('/dashboard');
    
  } catch (error) {
    console.error('🚨 Erro no login:', error);
    
    if (error instanceof Error) {
      // 🎯 Padronizar mensagem de erro (não dar dicas sobre contas existentes)
      if (error.message.includes('incorretos') || 
          error.message.includes('inválido') ||
          error.message.includes('404') ||
          error.message.includes('401')) {
        return {
          success: false,
          error: 'Email ou senha incorretos.',
        };
      }
      
      return {
        success: false,
        error: 'Erro ao fazer login. Tente novamente.',
      };
    }

    return {
      success: false,
      error: 'Erro interno do servidor. Tente novamente.',
    };
  }
}

// 📧 Server Action para Solicitação de Reset de Senha
export async function requestPasswordResetAction(
  formData: FormData
): Promise<ActionResult> {
  try {
    const rawData = {
      email: formData.get('email') as string,
    };

    // 🔍 Validação server-side
    const validationResult = requestPasswordResetSchema.safeParse(rawData);
    
    if (!validationResult.success) {
      const errorMessages = validationResult.error.errors
        .map((err) => `${err.path.join('.')}: ${err.message}`)
        .join(', ');
      
      return {
        success: false,
        error: errorMessages,
      };
    }

    const { email } = validationResult.data;

    // 🌐 Solicitar reset via API
    await api.requestPasswordReset(email);

    // ✅ Sempre retornar sucesso (não vazar informações sobre contas existentes)
    return {
      success: true,
      data: { message: 'Se o email existir, você receberá instruções para redefinir sua senha.' },
    };
    
  } catch (error) {
    console.error('🚨 Erro no reset de senha:', error);
    
    // 🛡️ Por segurança, sempre retornar sucesso (não vazar se email existe)
    return {
      success: true,
      data: { message: 'Se o email existir, você receberá instruções para redefinir sua senha.' },
    };
  }
}

// 💡 Exemplo de uso em componentes:
// 
// <form action={signInAction}>
//   <input name="email" type="email" required />
//   <input name="password" type="password" required />
//   <button type="submit">Entrar</button>
// </form>
//
// Ou com React Hook Form:
// 
// const { handleSubmit } = useForm();
// const onSubmit = async (data) => {
//   const formData = new FormData();
//   Object.entries(data).forEach(([key, value]) => {
//     formData.append(key, value);
//   });
//   const result = await signInAction(formData);
//   if (!result.success) {
//     setError(result.error);
//   }
// };

📝 Componente de Formulário de Login

🔥 Features Implementadas:

  • React Hook Form: Performance otimizada com uncontrolled inputs
  • Zod validation: Validação client-side em tempo real
  • Server Actions: Segurança server-side
  • Loading states: UX durante submissão
  • Error handling: Feedback visual para erros
components/forms/login-form.tsx
'use client';

import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { signInSchema, type SignInFormData } from '@/lib/auth-validations';
import { signInAction } from '@/lib/auth-actions';
import { useAuthStore } from '@/store/auth-store';

export function LoginForm() {
  const router = useRouter();
  const [isLoading, setIsLoading] = useState(false);
  const [serverError, setServerError] = useState<string | null>(null);
  
  // 🏪 Zustand store para atualizar estado global
  const signIn = useAuthStore((state) => state.signIn);

  // ⚡ Configuração do React Hook Form com Zod
  const {
    register,           // Registra inputs no formulário
    handleSubmit,       // Handler para submissão
    formState: { 
      errors,          // Erros de validação
      isSubmitting,    // Estado de submissão
      isValid          // Se formulário é válido
    },
    setError,          // Definir erros programaticamente
    clearErrors,       // Limpar erros
  } = useForm<SignInFormData>({
    resolver: zodResolver(signInSchema), // 🔍 Integração com Zod
    mode: 'onChange',                    // Validar ao digitar
    defaultValues: {
      email: '',
      password: '',
    },
  });

  // 🎯 Handler de submissão (híbrido: client + server)
  const onSubmit = async (data: SignInFormData) => {
    try {
      setIsLoading(true);
      setServerError(null);
      clearErrors();

      // 🔄 Abordagem híbrida: tentar Zustand primeiro, fallback para Server Action
      try {
        // 1️⃣ Tentar login via Zustand (mais rápido, melhor UX)
        await signIn(data.email, data.password);
        router.push('/dashboard');
        return;
        
      } catch (zustandError) {
        console.warn('⚠️ Login via Zustand falhou, tentando Server Action...');
        
        // 2️⃣ Fallback: usar Server Action
        const formData = new FormData();
        formData.append('email', data.email);
        formData.append('password', data.password);
        
        const result = await signInAction(formData);
        
        if (!result.success) {
          throw new Error(result.error || 'Erro ao fazer login');
        }
        
        // Se chegou aqui, Server Action redirecionou com sucesso
      }
      
    } catch (error) {
      // 🚨 Tratamento de erros
      console.error('🚨 Erro no login:', error);
      
      if (error instanceof Error) {
        // 🎯 Classificar tipos de erro para melhor UX
        if (error.message.includes('network') || error.message.includes('fetch')) {
          setServerError('Erro de conexão. Verifique sua internet e tente novamente.');
        } else if (error.message.includes('401') || error.message.includes('incorretos')) {
          setError('email', { 
            type: 'manual', 
            message: 'Email ou senha incorretos' 
          });
          setError('password', { 
            type: 'manual', 
            message: 'Email ou senha incorretos' 
          });
        } else {
          setServerError(error.message);
        }
      } else {
        setServerError('Erro inesperado. Tente novamente.');
      }
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className="w-full max-w-md mx-auto p-6 bg-gray-900 rounded-lg border border-gray-700">
      <h2 className="text-2xl font-bold text-white mb-6 text-center">
        Entrar no Restaurantix
      </h2>

      {/* 🚨 Exibir erro do servidor */}
      {serverError && (
        <Alert className="mb-4 border-red-500 bg-red-500/10">
          <AlertDescription className="text-red-400">
            {serverError}
          </AlertDescription>
        </Alert>
      )}

      {/* 📝 Formulário */}
      <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
        {/* 📧 Campo Email */}
        <div>
          <Label htmlFor="email" className="text-white">
            Email
          </Label>
          <Input
            id="email"
            type="email"
            placeholder="seu@email.com"
            className="bg-gray-800 border-gray-600 text-white"
            {...register('email')} // 🔗 Registra no React Hook Form
            disabled={isLoading}
          />
          {/* ❌ Exibir erro de validação */}
          {errors.email && (
            <p className="text-red-400 text-sm mt-1">
              {errors.email.message}
            </p>
          )}
        </div>

        {/* 🔒 Campo Senha */}
        <div>
          <Label htmlFor="password" className="text-white">
            Senha
          </Label>
          <Input
            id="password"
            type="password"
            placeholder="sua senha"
            className="bg-gray-800 border-gray-600 text-white"
            {...register('password')} // 🔗 Registra no React Hook Form
            disabled={isLoading}
          />
          {/* ❌ Exibir erro de validação */}
          {errors.password && (
            <p className="text-red-400 text-sm mt-1">
              {errors.password.message}
            </p>
          )}
        </div>

        {/* 🚀 Botão de Submit */}
        <Button
          type="submit"
          className="w-full bg-blue-600 hover:bg-blue-700"
          disabled={isLoading || isSubmitting || !isValid}
        >
          {isLoading ? (
            <>
              <div className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full mr-2" />
              Entrando...
            </>
          ) : (
            'Entrar'
          )}
        </Button>
      </form>

      {/* 🔗 Links auxiliares */}
      <div className="mt-4 text-center space-y-2">
        <p className="text-gray-400 text-sm">
          Esqueceu sua senha?{' '}
          <button 
            onClick={() => router.push('/forgot-password')}
            className="text-blue-400 hover:underline"
          >
            Clique aqui
          </button>
        </p>
        <p className="text-gray-400 text-sm">
          Não tem conta?{' '}
          <button 
            onClick={() => router.push('/cadastro')}
            className="text-blue-400 hover:underline"
          >
            Cadastre-se
          </button>
        </p>
      </div>
    </div>
  );
}

// 💡 Principais benefícios desta implementação:
//
// 🎯 Performance:
// - React Hook Form usa uncontrolled inputs (menos re-renders)
// - Validação acontece apenas quando necessário
// - Zustand evita prop drilling
//
// 🛡️ Segurança:
// - Validação client + server (dupla proteção)
// - Server Actions protegem contra CSRF
// - Não vaza informações sobre contas existentes
//
// 🎨 UX:
// - Feedback visual imediato (loading, erros)
// - Validação em tempo real
// - Mensagens de erro claras e acionáveis
//
// 🔧 Manutenibilidade:
// - Schemas reutilizáveis
// - Tipos TypeScript automáticos
// - Separação clara de responsabilidades

🏗️ Arquitetura da Validação (Client + Server)

Client-Side (React Hook Form + Zod):

  • • Feedback imediato ao usuário
  • • Menos requisições desnecessárias
  • • Melhor experiência de usuário
  • • Validação em tempo real

Server-Side (Server Actions + Zod):

  • • Segurança que não pode ser bypassada
  • • Proteção contra ataques maliciosos
  • • Validação final antes do banco
  • • Rate limiting e throttling

✅ Benefícios da Abordagem Híbrida

  • 🚀 Performance: Uncontrolled inputs, menos re-renders
  • 🔒 Segurança: Validação dupla (client + server)
  • 🎨 UX: Feedback imediato e states visuais
  • 📱 Acessibilidade: Progressive enhancement
  • 🔧 Manutenível: Schemas reutilizáveis
  • 🎯 Type-safe: TypeScript end-to-end
  • 🧪 Testável: Lógica isolada e pura
  • 📈 Escalável: Padrões consistentes
Fluxos de Esqueci/Resetar Senha

🔄 O Fluxo Completo de Reset de Senha

Um sistema seguro de reset de senha precisa ser impossível de explorar, user-friendly e temporalmente limitado.

Princípios de Segurança:

  • • Não vazar informações sobre contas existentes
  • • Tokens únicos, criptograficamente seguros
  • • Expiração temporal obrigatória
  • • Rate limiting para prevenir spam

Experiência do Usuário:

  • • Feedback claro sobre o que fazer
  • • Email com instruções detalhadas
  • • URL de reset que expira
  • • Confirmação após reset bem-sucedido

🎯 Fluxo Técnico Passo a Passo

1

Usuário solicita reset

Envia email no formulário "Esqueci minha senha"

2

Servidor valida e gera token

Token único (UUID/JWT) com expiração de 15 minutos

3

Email enviado (sempre)

Mesmo se email não existir (não vazar informações)

4

Usuário clica no link

Redirecionado para página de nova senha com token

5

Nova senha definida

Token invalidado, hash da senha atualizado no banco

📧 Formulário "Esqueci Minha Senha"

🎨 Design Patterns Implementados:

  • Information Hiding: Não revela se email existe ou não
  • Progressive Disclosure: Mostra próximos passos após submissão
  • Error Prevention: Validação em tempo real
  • Feedback: Estados visuais claros (loading, success, error)
components/forms/forgot-password-form.tsx
'use client';

import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { CheckCircle, Mail, AlertCircle } from 'lucide-react';
import { 
  requestPasswordResetSchema, 
  type RequestPasswordResetFormData 
} from '@/lib/auth-validations';
import { requestPasswordResetAction } from '@/lib/auth-actions';

export function ForgotPasswordForm() {
  const [isLoading, setIsLoading] = useState(false);
  const [isSuccess, setIsSuccess] = useState(false);
  const [serverError, setServerError] = useState<string | null>(null);

  // ⚡ React Hook Form com Zod validation
  const {
    register,
    handleSubmit,
    formState: { errors, isValid },
    watch,
  } = useForm<RequestPasswordResetFormData>({
    resolver: zodResolver(requestPasswordResetSchema),
    mode: 'onChange', // Validar em tempo real
    defaultValues: {
      email: '',
    },
  });

  // 👀 Observar valor do email para UX dinâmica
  const emailValue = watch('email');

  // 🎯 Handler de submissão
  const onSubmit = async (data: RequestPasswordResetFormData) => {
    try {
      setIsLoading(true);
      setServerError(null);

      // 🌐 Criar FormData para Server Action
      const formData = new FormData();
      formData.append('email', data.email);

      // 📡 Chamar Server Action
      const result = await requestPasswordResetAction(formData);

      if (result.success) {
        // ✅ Sempre mostrar sucesso (segurança)
        setIsSuccess(true);
      } else {
        // ❌ Mostrar erro apenas se for erro de validação
        setServerError(result.error || 'Erro ao processar solicitação');
      }
    } catch (error) {
      console.error('🚨 Erro no forgot password:', error);
      setServerError('Erro inesperado. Tente novamente.');
    } finally {
      setIsLoading(false);
    }
  };

  // 🎉 Estado de sucesso
  if (isSuccess) {
    return (
      <div className="w-full max-w-md mx-auto p-6 bg-gray-900 rounded-lg border border-gray-700">
        <div className="text-center space-y-4">
          {/* ✅ Ícone de sucesso */}
          <div className="mx-auto w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center">
            <CheckCircle className="w-8 h-8 text-green-400" />
          </div>
          
          {/* 📧 Título e descrição */}
          <div className="space-y-2">
            <h2 className="text-xl font-bold text-white">
              Email Enviado!
            </h2>
            <p className="text-gray-300 text-sm leading-relaxed">
              Se existe uma conta com o email <strong className="text-blue-400">{emailValue}</strong>, 
              você receberá instruções para redefinir sua senha.
            </p>
          </div>

          {/* 🔍 Instruções adicionais */}
          <div className="bg-blue-900/20 p-4 rounded-lg border border-blue-400/30 text-left">
            <h4 className="text-blue-400 font-semibold mb-2 flex items-center">
              <Mail className="w-4 h-4 mr-2" />
              Próximos Passos:
            </h4>
            <ul className="text-gray-300 text-sm space-y-1">
              <li>• Verifique sua caixa de entrada</li>
              <li>• Clique no link de redefinição</li>
              <li>• O link expira em <strong className="text-yellow-400">15 minutos</strong></li>
              <li>• Verifique também a pasta de spam</li>
            </ul>
          </div>

          {/* 🔄 Botão para tentar novamente */}
          <Button
            onClick={() => {
              setIsSuccess(false);
              setServerError(null);
            }}
            variant="outline"
            className="w-full"
          >
            Tentar Novamente
          </Button>
        </div>
      </div>
    );
  }

  // 📝 Formulário de solicitação
  return (
    <div className="w-full max-w-md mx-auto p-6 bg-gray-900 rounded-lg border border-gray-700">
      <div className="text-center mb-6">
        <h2 className="text-2xl font-bold text-white mb-2">
          Esqueceu sua senha?
        </h2>
        <p className="text-gray-400 text-sm">
          Digite seu email e enviaremos instruções para redefinir sua senha.
        </p>
      </div>

      {/* 🚨 Erro do servidor */}
      {serverError && (
        <Alert className="mb-4 border-red-500 bg-red-500/10">
          <AlertCircle className="h-4 w-4" />
          <AlertDescription className="text-red-400">
            {serverError}
          </AlertDescription>
        </Alert>
      )}

      {/* 📝 Formulário */}
      <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
        <div>
          <Label htmlFor="email" className="text-white">
            Email
          </Label>
          <Input
            id="email"
            type="email"
            placeholder="seu@email.com"
            className="bg-gray-800 border-gray-600 text-white"
            {...register('email')}
            disabled={isLoading}
          />
          {/* ❌ Erro de validação */}
          {errors.email && (
            <p className="text-red-400 text-sm mt-1">
              {errors.email.message}
            </p>
          )}
        </div>

        {/* 🚀 Botão de submit */}
        <Button
          type="submit"
          className="w-full bg-orange-600 hover:bg-orange-700"
          disabled={isLoading || !isValid}
        >
          {isLoading ? (
            <>
              <div className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full mr-2" />
              Enviando...
            </>
          ) : (
            <>
              <Mail className="w-4 h-4 mr-2" />
              Enviar Instruções
            </>
          )}
        </Button>
      </form>

      {/* 🔗 Link para voltar ao login */}
      <div className="mt-4 text-center">
        <p className="text-gray-400 text-sm">
          Lembrou da senha?{' '}
          <a href="/login" className="text-blue-400 hover:underline">
            Voltar ao login
          </a>
        </p>
      </div>
    </div>
  );
}

// 💡 Principais características de segurança:
//
// 🛡️ Information Hiding:
// - Sempre retorna sucesso, independente de email existir
// - Não vaza informações sobre contas na base
//
// ⏰ Time-based Security:
// - Tokens expiram rapidamente (15 min)
// - Rate limiting no servidor previne spam
//
// 🎨 UX Considerations:
// - Estados visuais claros (loading, success, error)
// - Instruções detalhadas sobre próximos passos
// - Feedback sobre o que esperar

🔒 Formulário de Nova Senha (Reset)

🔐 Validações de Segurança:

  • Token validation: Verifica se token é válido e não expirou
  • Password strength: Força mínima de senha
  • Confirmation match: Senhas devem coincidir
  • One-time use: Token invalidado após uso
components/forms/reset-password-form.tsx
'use client';

import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { useRouter, useSearchParams } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Alert, AlertDescription } from '@/components/ui/alert';
import { CheckCircle, Lock, AlertTriangle } from 'lucide-react';
import { 
  resetPasswordSchema, 
  type ResetPasswordFormData 
} from '@/lib/auth-validations';
import { resetPasswordAction } from '@/lib/auth-actions';

export function ResetPasswordForm() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const [isLoading, setIsLoading] = useState(false);
  const [isSuccess, setIsSuccess] = useState(false);
  const [serverError, setServerError] = useState<string | null>(null);
  
  // 🔍 Extrair token da URL
  const token = searchParams.get('token');

  // ⚡ React Hook Form com validação robusta
  const {
    register,
    handleSubmit,
    formState: { errors, isValid },
    watch,
    setError,
  } = useForm<ResetPasswordFormData>({
    resolver: zodResolver(resetPasswordSchema),
    mode: 'onChange',
    defaultValues: {
      token: token || '',
      password: '',
      confirmPassword: '',
    },
  });

  // 👀 Observar senhas para validação visual
  const password = watch('password');
  const confirmPassword = watch('confirmPassword');

  // 🚨 Verificar se token existe
  useEffect(() => {
    if (!token) {
      setServerError('Token de reset inválido ou expirado.');
    }
  }, [token]);

  // 🎯 Handler de submissão
  const onSubmit = async (data: ResetPasswordFormData) => {
    try {
      setIsLoading(true);
      setServerError(null);

      // 🌐 Preparar FormData
      const formData = new FormData();
      formData.append('token', data.token);
      formData.append('password', data.password);
      formData.append('confirmPassword', data.confirmPassword);

      // 📡 Chamar Server Action
      const result = await resetPasswordAction(formData);

      if (result.success) {
        setIsSuccess(true);
      } else {
        // 🎯 Tratar diferentes tipos de erro
        if (result.error?.includes('token')) {
          setServerError('Token inválido ou expirado. Solicite um novo reset.');
        } else if (result.error?.includes('senha')) {
          setError('password', {
            type: 'manual',
            message: result.error,
          });
        } else {
          setServerError(result.error || 'Erro ao redefinir senha');
        }
      }
    } catch (error) {
      console.error('🚨 Erro no reset password:', error);
      setServerError('Erro inesperado. Tente novamente.');
    } finally {
      setIsLoading(false);
    }
  };

  // 🎉 Estado de sucesso
  if (isSuccess) {
    return (
      <div className="w-full max-w-md mx-auto p-6 bg-gray-900 rounded-lg border border-gray-700">
        <div className="text-center space-y-4">
          <div className="mx-auto w-16 h-16 bg-green-500/20 rounded-full flex items-center justify-center">
            <CheckCircle className="w-8 h-8 text-green-400" />
          </div>
          
          <div className="space-y-2">
            <h2 className="text-xl font-bold text-white">
              Senha Redefinida!
            </h2>
            <p className="text-gray-300 text-sm">
              Sua senha foi alterada com sucesso. Agora você pode fazer login com sua nova senha.
            </p>
          </div>

          <Button
            onClick={() => router.push('/login')}
            className="w-full bg-green-600 hover:bg-green-700"
          >
            Ir para Login
          </Button>
        </div>
      </div>
    );
  }

  // 🚨 Token inválido
  if (!token || serverError?.includes('Token')) {
    return (
      <div className="w-full max-w-md mx-auto p-6 bg-gray-900 rounded-lg border border-gray-700">
        <div className="text-center space-y-4">
          <div className="mx-auto w-16 h-16 bg-red-500/20 rounded-full flex items-center justify-center">
            <AlertTriangle className="w-8 h-8 text-red-400" />
          </div>
          
          <div className="space-y-2">
            <h2 className="text-xl font-bold text-white">
              Link Inválido
            </h2>
            <p className="text-gray-300 text-sm">
              Este link de redefinição é inválido ou expirou. 
              Solicite um novo reset de senha.
            </p>
          </div>

          <Button
            onClick={() => router.push('/forgot-password')}
            className="w-full bg-orange-600 hover:bg-orange-700"
          >
            Solicitar Novo Reset
          </Button>
        </div>
      </div>
    );
  }

  // 📝 Formulário de nova senha
  return (
    <div className="w-full max-w-md mx-auto p-6 bg-gray-900 rounded-lg border border-gray-700">
      <div className="text-center mb-6">
        <h2 className="text-2xl font-bold text-white mb-2">
          Nova Senha
        </h2>
        <p className="text-gray-400 text-sm">
          Digite sua nova senha abaixo. Certifique-se de que seja segura.
        </p>
      </div>

      {/* 🚨 Erro do servidor */}
      {serverError && !serverError.includes('Token') && (
        <Alert className="mb-4 border-red-500 bg-red-500/10">
          <AlertTriangle className="h-4 w-4" />
          <AlertDescription className="text-red-400">
            {serverError}
          </AlertDescription>
        </Alert>
      )}

      <form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
        {/* 🔐 Campo Token (hidden) */}
        <input
          type="hidden"
          {...register('token')}
        />

        {/* 🔒 Nova senha */}
        <div>
          <Label htmlFor="password" className="text-white">
            Nova Senha
          </Label>
          <Input
            id="password"
            type="password"
            placeholder="Sua nova senha"
            className="bg-gray-800 border-gray-600 text-white"
            {...register('password')}
            disabled={isLoading}
          />
          {errors.password && (
            <p className="text-red-400 text-sm mt-1">
              {errors.password.message}
            </p>
          )}
          
          {/* 💪 Indicador de força da senha */}
          {password && (
            <div className="mt-2">
              <div className="flex space-x-1">
                <div className={`h-1 flex-1 rounded ${password.length >= 6 ? 'bg-green-500' : 'bg-gray-600'}`} />
                <div className={`h-1 flex-1 rounded ${password.length >= 8 ? 'bg-green-500' : 'bg-gray-600'}`} />
                <div className={`h-1 flex-1 rounded ${/[A-Z]/.test(password) ? 'bg-green-500' : 'bg-gray-600'}`} />
                <div className={`h-1 flex-1 rounded ${/[0-9]/.test(password) ? 'bg-green-500' : 'bg-gray-600'}`} />
              </div>
              <p className="text-xs text-gray-400 mt-1">
                Força da senha: {password.length >= 8 && /[A-Z]/.test(password) && /[0-9]/.test(password) ? 'Forte' : 'Fraca'}
              </p>
            </div>
          )}
        </div>

        {/* 🔒 Confirmar senha */}
        <div>
          <Label htmlFor="confirmPassword" className="text-white">
            Confirmar Nova Senha
          </Label>
          <Input
            id="confirmPassword"
            type="password"
            placeholder="Digite novamente"
            className="bg-gray-800 border-gray-600 text-white"
            {...register('confirmPassword')}
            disabled={isLoading}
          />
          {errors.confirmPassword && (
            <p className="text-red-400 text-sm mt-1">
              {errors.confirmPassword.message}
            </p>
          )}
          
          {/* ✅ Indicador de senhas iguais */}
          {password && confirmPassword && (
            <p className={`text-xs mt-1 ${password === confirmPassword ? 'text-green-400' : 'text-red-400'}`}>
              {password === confirmPassword ? '✅ Senhas coincidem' : '❌ Senhas não coincidem'}
            </p>
          )}
        </div>

        <Button
          type="submit"
          className="w-full bg-blue-600 hover:bg-blue-700"
          disabled={isLoading || !isValid}
        >
          {isLoading ? (
            <>
              <div className="animate-spin h-4 w-4 border-2 border-white border-t-transparent rounded-full mr-2" />
              Redefinindo...
            </>
          ) : (
            <>
              <Lock className="w-4 h-4 mr-2" />
              Redefinir Senha
            </>
          )}
        </Button>
      </form>
    </div>
  );
}

// 🔐 Características de segurança implementadas:
//
// 🛡️ Token Security:
// - Validação de token obrigatória
// - Tokens de uso único (invalidados após uso)
// - Expiração temporal (15 min)
//
// 🔒 Password Security:
// - Validação de força em tempo real
// - Confirmação obrigatória
// - Hash seguro no servidor
//
// 🎨 UX Excellence:
// - Feedback visual sobre força da senha
// - Estados claros (loading, success, error)
// - Mensagens específicas para cada tipo de erro

🔄 Considerações de Segurança vs UX

🛡️ Segurança Primeiro:

  • • Nunca revelar se email existe
  • • Tokens únicos e não previsíveis
  • • Expiração temporal agressiva
  • • Rate limiting para prevenir ataques
  • • Logs de tentativas suspeitas

🎨 UX Balanceada:

  • • Feedback claro sobre próximos passos
  • • Instruções detalhadas no email
  • • Estados visuais informativos
  • • Fallbacks para casos de erro
  • • Tempo adequado para completar

✅ Melhores Práticas Implementadas

  • 🔐 Security by Design: Segurança como requisito, não adição
  • ⏰ Time-bounded: Tokens com expiração apropriada
  • 📧 Email Templates: Instruções claras e branded
  • 🎯 Error Handling: Mensagens específicas mas seguras
  • 🧪 Testável: Fluxos isolados e determinísticos
  • 📊 Monitorável: Logs para detecção de ataques
  • ♿ Acessível: Compatible com screen readers
  • 📱 Responsivo: Funciona em todos dispositivos
Páginas de autenticação

src/app/page.tsx

'use client';

import { useAuthStore } from '@/stores/auth-store';
import { useShallow } from 'zustand/react/shallow';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { ArrowRight, ChefHat, Loader2, Users } from 'lucide-react';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { redirect } from 'next/navigation';
import { useAuthInit } from '@/hooks/use-auth-init';

export default function HomePage() {
  const { user, isLoading, isAuthenticated } = useAuthStore(
    useShallow((state) => ({
      user: state.user,
      isLoading: state.isLoading,
      isAuthenticated: state.isAuthenticated,
    })),
  );

  useAuthInit();

  if (isLoading) {
    return (
      <div className="min-h-screen flex items-center justify-center">
        <div className="text-center">
          <Loader2 className="h-8 w-8 animate-spin mx-auto mb-4" />
          <p>Carregando...</p>
        </div>
      </div>
    );
  }

  if (isAuthenticated && user) {
    redirect('/dashboard');
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-orange-50 to-red-50">
      <main className="container mx-auto px-4 py-16">
        <div className="text-center mb-16">
          <h1 className="text-5xl font-bold mb-6 bg-gradient-to-r from-orange-600 to-red-600 bg-clip-text text-transparent">
            Restaurantix
          </h1>
          <p className="text-xl text-muted-foreground mb-8 max-w-2xl mx-auto">
            Sistema completo de gestão para restaurantes. Gerencie pedidos, acompanhe métricas e
            otimize suas operações.
          </p>

          <div className="flex flex-col sm:flex-row gap-4 justify-center">
            <Button asChild size="lg" className="bg-orange-600 hover:bg-orange-700">
              <Link href="/sign-in">
                Fazer Login
                <ArrowRight className="ml-2 h-4 w-4" />
              </Link>
            </Button>
            <Button asChild variant="outline" size="lg">
              <Link href="/sign-up">Criar Conta</Link>
            </Button>
          </div>
        </div>

        <div className="grid md:grid-cols-2 gap-8 max-w-4xl mx-auto">
          <Card className="border-orange-200">
            <CardHeader>
              <CardTitle className="flex items-center text-orange-700">
                <ChefHat className="h-6 w-6 mr-2" />
                Para Gerentes
              </CardTitle>
            </CardHeader>
            <CardContent>
              <ul className="space-y-2 text-muted-foreground">
                <li>• Gerencie pedidos em tempo real</li>
                <li>• Acompanhe métricas de vendas</li>
                <li>• Controle estoque e cardápio</li>
                <li>• Relatórios detalhados</li>
              </ul>
            </CardContent>
          </Card>

          <Card className="border-red-200">
            <CardHeader>
              <CardTitle className="flex items-center text-red-700">
                <Users className="h-6 w-6 mr-2" />
                Para Clientes
              </CardTitle>
            </CardHeader>
            <CardContent>
              <ul className="space-y-2 text-muted-foreground">
                <li>• Faça pedidos online</li>
                <li>• Acompanhe status do pedido</li>
                <li>• Histórico de compras</li>
                <li>• Avaliações e feedback</li>
              </ul>
            </CardContent>
          </Card>
        </div>
      </main>
    </div>
  );
}

              

src/app/sign-in/page.tsx

'use client';

import { Suspense } from 'react';
import { SignInForm } from '@/components/auth/sign-in-form';
import { Card, CardContent } from '@/components/ui/card';
import Link from 'next/link';
import { useSearchParams } from 'next/navigation';
import { useEffect } from 'react';
import { toast } from 'sonner';

function SignInContent() {
  const searchParams = useSearchParams();

  useEffect(() => {
    const error = searchParams.get('error');
    const details = searchParams.get('details');

    if (error) {
      switch (error) {
        case 'missing_cookies':
          toast.error('Erro de autenticação: cookies não encontrados. Tente novamente.');
          break;
        case 'missing_code_or_state':
          toast.error('Erro de autenticação: parâmetros inválidos.');
          break;
        case 'invalid_state':
          toast.error('Erro de autenticação: sessão inválida.');
          break;
        case 'google_error':
          toast.error(`Erro do Google: ${details || 'Erro desconhecido'}`);
          break;
        case 'auth_failed':
          toast.error('Falha na autenticação. Tente novamente.');
          break;
        default:
          toast.error('Erro ao fazer login. Tente novamente.');
      }
    }
  }, [searchParams]);

  return (
    <div className="flex min-h-svh flex-col items-center justify-center bg-muted p-6 md:p-10">
      <div className="w-full max-w-sm md:max-w-3xl">
        <div className="flex flex-col gap-6">
          <Card className="overflow-hidden">
            <CardContent className="grid p-0 md:grid-cols-2">
              <div className="p-6 md:p-8">
                <div className="flex flex-col gap-6">
                  <div className="flex flex-col items-center text-center">
                    <h1 className="text-2xl font-bold">Bem-vindo de volta</h1>
                    <p className="text-balance text-muted-foreground">
                      Entre na sua conta Restaurantix
                    </p>
                  </div>
                  <SignInForm />
                </div>
              </div>
              <div className="relative hidden bg-muted md:block">
                <img
                  src="https://images.unsplash.com/photo-1517248135467-4c7edcad34c4?w=800&h=1000&fit=crop"
                  alt="Interior de restaurante moderno"
                  className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
                />
              </div>
            </CardContent>
          </Card>
          <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary">
            Ao clicar em continuar, você concorda com nossos{' '}
            <Link href="/terms">Termos de Serviço</Link> e{' '}
            <Link href="/privacy">Política de Privacidade</Link>.
          </div>
        </div>
      </div>
    </div>
  );
}

export default function SignInPage() {
  return (
    <Suspense fallback={<div>Carregando...</div>}>
      <SignInContent />
    </Suspense>
  );
}

              

src/app/sign-up/page.tsx

'use client';

import { Suspense, useEffect } from 'react';
import { SignUpForm } from '@/components/auth/sign-up-form';
import { useSearchParams } from 'next/navigation';
import { toast } from 'sonner';

function SignUpContent() {
  const searchParams = useSearchParams();

  useEffect(() => {
    const error = searchParams.get('error');
    const details = searchParams.get('details');

    if (error) {
      switch (error) {
        case 'missing_cookies':
          toast.error('Erro de autenticação: cookies não encontrados. Tente novamente.');
          break;
        case 'missing_code_or_state':
          toast.error('Erro de autenticação: parâmetros inválidos.');
          break;
        case 'invalid_state':
          toast.error('Erro de autenticação: sessão inválida.');
          break;
        case 'google_error':
          toast.error(`Erro do Google: ${details || 'Erro desconhecido'}`);
          break;
        case 'auth_failed':
          toast.error('Falha na autenticação. Tente novamente.');
          break;
        default:
          toast.error('Erro ao fazer cadastro. Tente novamente.');
      }
    }
  }, [searchParams]);

  return (
    <div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
      <div className="w-full max-w-sm md:max-w-3xl">
        <SignUpForm />
      </div>
    </div>
  );
}

export default function SignUpPage() {
  return (
    <Suspense fallback={<div>Carregando...</div>}>
      <SignUpContent />
    </Suspense>
  );
}

              

src/app/forgot-password/page.tsx

import { ForgotPasswordForm } from '@/components/auth/forgot-password-form';

export default function ForgotPasswordPage() {
  return (
    <div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
      <div className="w-full max-w-sm md:max-w-3xl">
        <ForgotPasswordForm />
      </div>
    </div>
  );
}

              

src/app/reset-password/page.tsx

import { Suspense } from 'react';
import { ResetPasswordForm } from '@/components/auth/reset-password-form';

export default function ResetPasswordPage() {
  return (
    <div className="bg-muted flex min-h-svh flex-col items-center justify-center p-6 md:p-10">
      <div className="w-full max-w-sm md:max-w-3xl">
        <Suspense fallback={<div>Carregando...</div>}>
          <ResetPasswordForm />
        </Suspense>
      </div>
    </div>
  );
}

              

src/app/dashboard/page.tsx

import { cookies } from 'next/headers';

async function getData() {
  const allCookies = await cookies();
  const csrf = allCookies.get('csrf');
  const auth = allCookies.get('auth');
  console.log(allCookies);
  return { csrf, auth };
}

export default async function DashboardPage() {
  const data = await getData();
  console.log(data);
  return <div className="container mx-auto px-4 py-8">Dashboard</div>;
}

              
Conclusão: Sistema de Autenticação Completo

Parabéns! Você implementou um sistema de autenticação profissional

Nesta aula, você construiu do zero um sistema de autenticação robusto e seguro, seguindo as melhores práticas da indústria. Este conhecimento é fundamental para qualquer aplicação web moderna e te prepara para projetos reais.

🛡️ Aspectos de Segurança Dominados

  • CSRF Protection: Proteção contra ataques Cross-Site Request Forgery
  • HttpOnly Cookies: Tokens inacessíveis via JavaScript (anti-XSS)
  • Validação Dupla: Client-side + Server-side validation
  • Information Hiding: Não vaza dados sobre contas existentes
  • Temporal Security: Tokens com expiração e invalidação

⚡ Tecnologias e Padrões Aplicados

  • React Hook Form: Formulários performáticos e acessíveis
  • Zod Validation: Schema-first validation TypeScript-safe
  • Zustand Store: Estado global simples e eficiente
  • Server Actions: Server-side logic segura no Next.js
  • API Client: Comunicação HTTP robusta com retry logic

Conceitos Fundamentais Aprendidos

🏗️ Arquitetura

  • • Separação de responsabilidades
  • • Modularização de código
  • • Reutilização de componentes
  • • Abstração de complexidade

🔒 Segurança

  • • Defense in depth
  • • Princípio do menor privilégio
  • • Fail secure (falhe com segurança)
  • • Security by design

🎨 UX/UI

  • • Progressive enhancement
  • • Loading states
  • • Error handling gracioso
  • • Feedback visual

📋 Checklist de Implementação Completa

Backend Integration
  • ✅ Variáveis de ambiente validadas
  • ✅ Route handlers implementados
  • ✅ Cookie propagation funcionando
  • ✅ CSRF tokens sendo gerenciados
  • ✅ Error handling robusto
State Management
  • ✅ Zustand store configurado
  • ✅ Persistência implementada
  • ✅ Actions assíncronas
  • ✅ Error boundaries
  • ✅ Loading states
Forms & Validation
  • ✅ React Hook Form integrado
  • ✅ Zod schemas reutilizáveis
  • ✅ Server Actions seguras
  • ✅ Real-time validation
  • ✅ TypeScript safety
Security Features
  • ✅ Password reset flow
  • ✅ CSRF protection
  • ✅ Input sanitization
  • ✅ Rate limiting ready
  • ✅ Secure by default

Próximos Passos e Melhorias

🚀 Melhorias de Performance

  • • Implementar React Query para cache
  • • Otimizar re-renders com useMemo
  • • Lazy loading de componentes
  • • Service Workers para offline
  • • Compressão de assets

🔒 Segurança Avançada

  • • Implementar 2FA/MFA
  • • Rate limiting por IP
  • • Login audit logs
  • • Detecção de dispositivos
  • • Security headers (HSTS, CSP)

💻 Código de Exemplo: Middleware de Autenticação

Bonus: Como implementar um middleware para proteger rotas privadas:

src/middleware.ts
// 📁 src/middleware.ts - Middleware global do Next.js
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// 🛡️ Rotas que requerem autenticação
const protectedRoutes = [
  '/dashboard',
  '/profile',
  '/settings',
  '/admin',
];

// 🔓 Rotas públicas (apenas para usuários não autenticados)
const publicRoutes = [
  '/login',
  '/signup',
  '/forgot-password',
  '/reset-password',
];

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // 🍪 Verificar se tem cookie de autenticação
  const authCookie = request.cookies.get('auth-token');
  const isAuthenticated = !!authCookie?.value;
  
  // 🔍 Verificar se é rota protegida
  const isProtectedRoute = protectedRoutes.some(route => 
    pathname.startsWith(route)
  );
  
  // 🔍 Verificar se é rota pública
  const isPublicRoute = publicRoutes.some(route => 
    pathname.startsWith(route)
  );
  
  // 🚫 Redirecionar não autenticados de rotas protegidas
  if (isProtectedRoute && !isAuthenticated) {
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('redirect', pathname); // Salvar destino original
    return NextResponse.redirect(loginUrl);
  }
  
  // 🔄 Redirecionar autenticados de rotas públicas
  if (isPublicRoute && isAuthenticated) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }
  
  // ✅ Permitir acesso
  return NextResponse.next();
}

// ⚙️ Configuração - quais rotas o middleware deve processar
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

// 💡 Como usar no componente:
// 
// export default function ProtectedPage() {
//   const { user, isLoading } = useAuthStore();
//   
//   if (isLoading) return <LoadingSpinner />;
//   
//   // Middleware já garantiu que user existe aqui
//   return <DashboardContent user={user} />;
// }

🎯 O que Você Conquistou Nesta Aula

🎖️ Habilidades Técnicas

  • • Arquitetura de autenticação segura
  • • Integração de múltiplas tecnologias
  • • Padrões de segurança da indústria
  • • TypeScript avançado
  • • Error handling profissional

🧠 Mindset de Desenvolvedor

  • • Pensamento em segurança primeiro
  • • Planejamento de arquitetura
  • • Código limpo e manutenível
  • • Experiência do usuário
  • • Debugging sistemático

🚀 Continue Sua Jornada

Você agora tem as bases sólidas para construir sistemas de autenticação profissionais. Este conhecimento é transferível para qualquer stack tecnológica e te coloca num nível avançado de desenvolvimento web.

Next.js ExpertSecurity AwareTypeScript ProFull-Stack Ready
Navegação do Curso

🎯 Sua Jornada no Módulo 2: Fundamentos Avançados

Aula 1

Fundamentos

Aula 2

Roteamento

Aula 3

UI/UX

4

Aula 4

Autenticação

Aula Anterior

Aula 3: Componentes e Styling

Construção de interfaces modernas com Shadcn/UI, Tailwind CSS e componentes reutilizáveis. Design system profissional.

Sistema de Design Completo
Tailwind CSS Avançado
Componentes Shadcn/UI
Próxima Aula

Aula 5: Database & Prisma ORM

Integração com banco de dados PostgreSQL usando Prisma ORM. Migrations, schemas e operações CRUD avançadas.

Prisma Schema & Migrations
PostgreSQL Integration
Type-safe Database Operations

Dicas para Maximizar seu Aprendizado

Pratique Ativamente

Não apenas assista. Implemente cada conceito em seu projeto. A programação se aprende fazendo.

?

Questione Tudo

Por que esta abordagem? Existem alternativas? Questionamentos levam a compreensão profunda.

Compartilhe

Ensine outros ou discuta conceitos em comunidades. Ensinar é a melhor forma de consolidar conhecimento.

Progresso do Módulo 2

4 de 8 aulas concluídas

50%

🎯 Próximos tópicos: Database, Deploy, Testing, Performance