Fluxo de Autenticação
Visão geral
O auth usa ApiClient no server e no client, com sessão baseada em cookie jwt_token HttpOnly.
┌──────────────────────────────────────────────┐
│ Server-side (loader) │
│ api.server.ts → ApiClient com/sem token │
│ Usado para: brand loading, auth profile │
├──────────────────────────────────────────────┤
│ Client-side (browser) │
│ auth.client.ts → ApiClient sem ler cookie │
│ Usado para: ações de UI pós-login │
└──────────────────────────────────────────────┘
Fluxo de inicialização
DefaultLayout monta
├── <AuthInitializer />
│ ├── useAuthInit():
│ │ └── Recebe auth do loader SSR → sincroniza user/userInfo no Zustand + marca hydrated
│ │ (NÃO chama initAuthService — apenas sync de dados)
│ ├── useAuthProfileSync():
│ │ └── Mantém profile atualizado quando necessário
│ └── useValidationRuntimeSync():
│ └── Computa allValidations e sincroniza com store
│
├── <WalletInitializer />
│ └── Roda em paralelo com AuthInitializer
│ └── Inicializa wallet state (saldo, transações)
O AuthInitializer chama 3 hooks: useAuthInit, useAuthProfileSync e useValidationRuntimeSync. O WalletInitializer roda ao lado do AuthInitializer, não dentro dele.
Login
Login e registro passam por API routes no server, não por chamadas diretas ao authService:
LoginModal
├── Usuário preenche email/CPF + senha
├── POST /api/auth/login (API route no server)
│ └── Server: authService.login() + Set-Cookie HttpOnly
├── API retorna: { user, userInfo, type? }
│ ├── type === 'two_factor_code' → modal 2FA (TODO)
│ ├── type === 'cancelled_account' → aviso conta cancelada
│ └── sem type → sucesso
├── setUser(user, userInfo) no Zustand
└── Fecha modal
Registro dinâmico (single-step)
RegisterModal
├── Usuário preenche formulário dinâmico (email, senha, telefone, documento, etc. conforme authConfig)
├── POST /api/auth/register (API route no server)
│ └── Server: valida payload + resolve endpoint em runtime
│ ├── /bff/register-simplified
│ ├── /auth/register
│ ├── /auth/register/simplified
│ └── /bff/social/{provider}/registerSimplified
│ + Set-Cookie HttpOnly
├── API retorna: { user, userInfo } (mesmo formato do login)
├── setUser(user, userInfo) no Zustand
└── Fecha modal
O fluxo social inicia em /api/auth/social/:provider (route interna do template), que redireciona para o provider BFF e retorna ao modal com os parâmetros do callback.
Nota: tanto login quanto registro passam pelas API routes (/api/auth/login, /api/auth/register) para que o cookie HttpOnly seja setado no server-side. O client nunca manipula o token diretamente.
Token storage
- Cookie:
jwt_token, 30 dias,path=/,SameSite=Lax,HttpOnly,Secure - Leitura do token: somente no server (
getTokenFromRequest) - Zustand: mantém
user,userInfo,isAuthenticated,hydrated,authModal
Logout
Header → botão "Sair"
├── POST /api/auth/logout (API route no server)
│ └── Server: authService.logout() + Set-Cookie de remoção (HttpOnly)
├── clearAuth() no Zustand
│ └── Também chama useWalletStore.getState().clearWallet() (side effect cross-store)
clearAuth — Side effect cross-store
O clearAuth() da auth store também limpa a wallet store:
// auth.ts
clearAuth: () => {
set({ user: null, userInfo: null, isAuthenticated: false });
useWalletStore.getState().clearWallet();
}
Isso garante que dados financeiros do usuário anterior não permaneçam no estado após logout ou expiração de sessão.
Tratamento de 401
Qualquer request protegido no server que falhar em autenticação resulta em estado não autenticado no loader/action.
No client, a UI reflete esse estado via clearAuth() e novo ciclo de loader.
Validações pós-login
Após o login (ou page refresh com cookie), o sistema de validações avalia automaticamente se o usuário tem pendências obrigatórias:
setUser(user, userInfo)
│
▼
useValidationRuntimeSync
├── buildValidationSnapshot(user, userInfo)
└── fetchAllValidations(config, snapshot)
│
▼
allValidations.hasPending?
├── force/regulatory/global → ValidationBlockerOverlay (overlay bloqueante)
└── false → navegação normal
Três paths de bloqueio (em ordem de prioridade):
- force —
forceRequestKyc = trueno perfil (backend forçou KYC) - regulatory —
showPendingDataFlow = trueno perfil (dados regulatórios pendentes, ex: endereço) - global —
config.global.active = truee módulos do global não satisfeitos
Quando há pendência, o DefaultLayout renderiza o ValidationBlockerOverlay sobre o site (com blur e anti-tamper). O usuário precisa completar as validações para continuar.
Além do bloqueio global, cada contexto (casino, deposit, withdraw, etc.) é avaliado separadamente via ValidationStepsModal quando o usuário tenta executar a ação correspondente.
Ver Validações para detalhes completos.
Arquivos relevantes
| Arquivo | Papel |
|---|---|
app/store/auth.ts | Zustand store: user, userInfo, isAuthenticated, hydrated, authModal + clearAuth (limpa wallet) |
app/store/wallet.ts | Zustand store: wallet state + clearWallet (chamado pelo clearAuth) |
app/services/auth.client.ts | Singleton AuthService + ApiClient client-side |
app/utils/cookie.server.ts | getTokenFromRequest, makeSetTokenHeader, makeDeleteTokenHeader |
app/hooks/useAuthInit.ts | Sync do auth do loader SSR para store (NÃO chama initAuthService) |
app/hooks/useAuthProfileSync.ts | Sync de profile atualizado |
app/hooks/useValidationRuntimeSync.ts | Computa allValidations e sincroniza com store |
app/components/auth/AuthInitializer.tsx | Componente invisível que chama useAuthInit + useAuthProfileSync + useValidationRuntimeSync |
app/components/wallet/WalletInitializer.tsx | Componente invisível que inicializa wallet (roda ao lado do AuthInitializer) |
app/components/auth/LoginModal.tsx | Modal de login (POST /api/auth/login) |
app/components/auth/RegisterModal.tsx | Modal de registro (POST /api/auth/register) |
app/components/layout/Header.tsx | Auth-aware: mostra user info ou botões |
app/store/validationRuntime.ts | Armazena resultado das validações |
app/components/validation/ValidationBlockerOverlay.tsx | Overlay bloqueante |