Architettura Autenticazione
Panoramica dell'architettura del sistema di autenticazione.
Componenti Principali
Il sistema è composto da:
| Componente | File | Descrizione |
|---|---|---|
| Zustand Store | app/store/auth/auth.store.ts | Gestione centralizzata dello stato con persist su localStorage |
| Auth Service | app/lib/auth/auth.ts | Logica di business per login/logout |
| Loaders | app/lib/auth/loaders.ts | Factory functions per proteggere le route |
| API Layer | app/api/auth/auth.api.ts | Chiamate HTTP al backend |
| Schemas | app/schemas/auth/auth.schema.ts | Validazione con Zod |
Sincronizzazione Multi-Tab
Il sistema sincronizza automaticamente lo stato di autenticazione tra tutte le schede/finestre del browser.
1. Zustand Persist (localStorage events)
- Sincronizzazione automatica dello stato tra tab
- Persiste
isAuthenticated,loginDataeuserSettings - Ripristina lo stato al reload della pagina
2. BroadcastChannel API (navigazione sincronizzata)
- Logout su una tab → logout istantaneo su tutte le tab + navigazione a login
- Login su una tab → sincronizzazione automatica + navigazione appropriata
- Selezione azienda → tutte le tab navigano al modulo di default o
/welcome - Ritorno alla lista aziende → tutte le tab navigano a
/companies - Più veloce e affidabile di storage events per la navigazione
- Supporta dati complessi (non solo stringhe)
Tipi di Messaggi Broadcast
| Messaggio | Quando | Effetto su altre tab |
|---|---|---|
login | Utente fa login | Sincronizza stato + naviga |
logout | Utente fa logout | Pulisce stato + naviga a login |
login_pending_confirmation | Login richiede conferma | Sincronizza loginData |
company_selected | Utente seleziona azienda | Carica nuovi settings + naviga |
back_to_companies | Ritorno a lista aziende | Naviga a /companies |
Implementazione
Il componente AuthSyncListener gestisce automaticamente la sincronizzazione quando viene montato in root.tsx.
Come Funziona il Loader
- L'utente naviga verso una route protetta
clientLoaderviene eseguito PRIMA del renderauthService.requireAuth()controlla l'autenticazione- Se autenticato → carica la route
- Se non autenticato → redirect a
/(login)
Il componente della route viene renderizzato solo se autorizzato.
Struttura dei File
app/
├── routes.ts # Configurazione route
├── lib/
│ ├── api.ts # Utility API (apiRequest, ApiError)
│ ├── query-client.ts # QueryClient TanStack Query
│ └── auth/
│ ├── auth.ts # Servizio di autenticazione
│ └── loaders.ts # Factory functions
├── store/
│ └── auth/
│ └── auth.store.ts # Zustand store con persist
├── api/
│ └── auth/
│ └── auth.api.ts # Funzioni API autenticazione
├── schemas/
│ ├── api.schema.ts # Schema base API
│ └── auth/
│ └── auth.schema.ts # Schema autenticazione
├── hooks/
│ ├── api/
│ │ └── auth/
│ │ └── useAuthApi.ts # TanStack Query hooks
│ └── sync/
│ ├── useBroadcastChannel.ts # Hook per sincronizzazione multi-tab
│ └── useAuthSyncListener.ts # Sync autenticazione tra tab
├── components/
│ └── layout/
│ └── authenticated.tsx # Layout per pagine protette
└── routes/
├── auth/
│ ├── login.tsx # Route con createGuestLoader()
│ └── companies.tsx # Route con createProtectedLoader()
├── welcome.tsx # Route con createProtectedLoader({ loadSettings: true })
└── r.$path.tsx # Route moduli con iframeAPI di Autenticazione
Uso dello Store Zustand (nei componenti React)
typescript
import { useAuthStore } from "@/store/auth/auth.store";
import { useNavigate } from "react-router";
function MyComponent() {
const navigate = useNavigate();
// Accesso ottimizzato con selector
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const userSettings = useAuthStore((state) => state.userSettings);
// Accesso alle actions
const logout = useAuthStore((state) => state.logout);
const handleLogout = () => {
logout();
navigate("/");
// AuthSyncListener gestisce il broadcast alle altre tab
};
return <div>{isAuthenticated ? "Logged in" : "Logged out"}</div>;
}Uso del Auth Service (fuori dai componenti React)
typescript
import { authService } from "@/lib/auth/auth";
// Verifica autenticazione
if (authService.isAuthenticated()) {
// Logica per utenti autenticati
}
// Login
const response = await authService.login(username, password);
if (response) {
const path = authService.getRedirectPath(response.data.redirect, response.data.module);
navigate(path);
}
// Logout completo
authService.logout();Data Fetching con TanStack Query
Raccomandato
typescript
import { useCompaniesList } from "@/hooks/api/auth/useAuthApi";
const { data, isLoading, error } = useCompaniesList();
// Nessun useEffect, nessun useRef necessario!Deprecato (non usare)
typescript
// NON usare useRef per prevenire doppi fetch
const hasFetchedRef = useRef(false);
useEffect(() => {
if (!hasFetchedRef.current) {
hasFetchedRef.current = true;
execute();
}
}, [execute]);Vantaggi TanStack Query
- Cache automatica (5 minuti default)
- StrictMode safe - De-duplicazione automatica
- Zero boilerplate - Nessun useEffect/useRef
- Invalidazioni coordinate - Mutations aggiornano query correlate
Quando useRef è OK
- BroadcastChannel (non-fetch)
- Riferimenti DOM
- Valori che non devono causare re-render
- MAI per prevenire fetch con TanStack Query
Best Practices Import
typescript
// Corretto - import diretto
import { useLogin } from "@/hooks/api/auth/useAuthApi";
// Sbagliato - NO barrel files
import { useLogin } from "@/hooks/api/auth"; // No index.tsRiferimenti
- Guida Rapida - Come proteggere le route
- Flusso Login - Diagramma del flusso
- API Reference - Funzioni API disponibili
- TanStack Query Guide - Documentazione completa data fetching