Skip to content

Architettura Autenticazione

Panoramica dell'architettura del sistema di autenticazione.

Componenti Principali

Il sistema è composto da:

ComponenteFileDescrizione
Zustand Storeapp/store/auth/auth.store.tsGestione centralizzata dello stato con persist su localStorage
Auth Serviceapp/lib/auth/auth.tsLogica di business per login/logout
Loadersapp/lib/auth/loaders.tsFactory functions per proteggere le route
API Layerapp/api/auth/auth.api.tsChiamate HTTP al backend
Schemasapp/schemas/auth/auth.schema.tsValidazione 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, loginData e userSettings
  • 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

MessaggioQuandoEffetto su altre tab
loginUtente fa loginSincronizza stato + naviga
logoutUtente fa logoutPulisce stato + naviga a login
login_pending_confirmationLogin richiede confermaSincronizza loginData
company_selectedUtente seleziona aziendaCarica nuovi settings + naviga
back_to_companiesRitorno a lista aziendeNaviga a /companies

Implementazione

Il componente AuthSyncListener gestisce automaticamente la sincronizzazione quando viene montato in root.tsx.


Come Funziona il Loader

  1. L'utente naviga verso una route protetta
  2. clientLoader viene eseguito PRIMA del render
  3. authService.requireAuth() controlla l'autenticazione
  4. Se autenticato → carica la route
  5. 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 iframe

API 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.ts

Riferimenti

Documentazione Elerama Frontend