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:
useAuthStoremanté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:
onUnauthorizedcallback é chamadoclearAuth()no Zustand → limpa token e userdeleteTokenCookie()→ remove cookie- Usuário volta a ver botões "Entrar" / "Registrar"
Arquivos relevantes
| Arquivo | Papel |
|---|---|
app/store/auth.ts | Zustand store: token, user, isAuthenticated, authModal, refreshState |
app/services/auth.client.ts | Singleton AuthService + ApiClient client-side |
app/utils/cookie.ts | getTokenFromCookie, setTokenCookie, deleteTokenCookie |
app/hooks/useAuthInit.ts | Init service → hydrate cookie → fetch profile |
app/components/auth/AuthInitializer.tsx | Componente invisível que chama useAuthInit |
app/components/auth/LoginModal.tsx | Modal de login |
app/components/auth/RegisterModal.tsx | Modal de registro |
app/components/layout/Header.tsx | Auth-aware: mostra user info ou botões |