Pular para o conteúdo principal

Fluxo de Autenticação

Visão geral

O auth usa dois ApiClient separados e persiste o token via cookie (não localStorage).

┌──────────────────────────────────────────────┐
│ Server-side (loader) │
│ api.server.ts → ApiClient SEM token │
│ Usado para: brand loading, config │
├──────────────────────────────────────────────┤
│ Client-side (browser) │
│ auth.client.ts → ApiClient COM token │
│ Usado para: login, profile, refresh, etc. │
└──────────────────────────────────────────────┘

Fluxo de inicialização

DefaultLayout monta
├── <AuthInitializer />
│ └── useAuthInit():
│ 1. initAuthService(env) → cria ApiClient client-side
│ - getAccessToken: lê do Zustand
│ - onUnauthorized: clearAuth() automático em 401
│ 2. hydrateFromCookie() → lê jwt_token do cookie → seta no Zustand
│ 3. Se tem token → getUserProfile() → setUser() ou clearAuth()

Login

LoginModal
├── Usuário preenche email/CPF + senha
├── authService.login({ login, password })
├── API retorna: { access_token, user, userInfo, type? }
│ ├── type === 'two_factor_code' → modal 2FA (TODO)
│ ├── type === 'cancelled_account' → aviso conta cancelada
│ └── sem type → sucesso
├── setAuth(token, user, userInfo) no Zustand
├── setTokenCookie(token) → cookie jwt_token, 30d, SameSite=Lax
└── Fecha modal

Registro simplificado

RegisterModal
├── Usuário preenche email + senha
├── authService.registerSimplified({ email, password, country, nationalities, optIn })
├── API retorna LoginResponse (mesmo formato do login)
├── setAuth(token, user, userInfo)
├── setTokenCookie(token)
└── Fecha modal

Token storage

  • Cookie: jwt_token, 30 dias, path=/, SameSite=Lax
  • Não httpOnly: precisa ler no client para enviar nos headers (mesmo padrão do Vue legado)
  • Zustand: useAuthStore mantém token em memória para acesso rápido

Token refresh

Lógica via pure functions (testáveis isoladamente):

// A cada 6h o token deve ser refreshed
shouldRefreshToken(state, now?)boolean
markTokenSaved(now?)TokenRefreshState
markRefreshStarted(state)TokenRefreshState
markRefreshCompleted(state, ok, now?)TokenRefreshState

O store expõe checkTokenRefresh(callback) que verifica se deve refreshar e executa o callback.

Logout

Header → botão "Sair"
├── authService.logout() → POST /auth/logout
├── clearAuth() no Zustand
├── deleteTokenCookie()

Tratamento de 401

Qualquer request que retorne 401 via auth.client.ts:

  1. onUnauthorized callback é chamado
  2. clearAuth() no Zustand → limpa token e user
  3. deleteTokenCookie() → remove cookie
  4. Usuário volta a ver botões "Entrar" / "Registrar"

Arquivos relevantes

ArquivoPapel
app/store/auth.tsZustand store: token, user, isAuthenticated, authModal, refreshState
app/services/auth.client.tsSingleton AuthService + ApiClient client-side
app/utils/cookie.tsgetTokenFromCookie, setTokenCookie, deleteTokenCookie
app/hooks/useAuthInit.tsInit service → hydrate cookie → fetch profile
app/components/auth/AuthInitializer.tsxComponente invisível que chama useAuthInit
app/components/auth/LoginModal.tsxModal de login
app/components/auth/RegisterModal.tsxModal de registro
app/components/layout/Header.tsxAuth-aware: mostra user info ou botões