React

Hook per l’autenticazione in SvelteKit: handle, locals e redirect

In SvelteKit l’autenticazione vive in hooks.server.ts: handle legge il cookie, riempie locals e il redirect sta nel load del gruppo protetto.

In sintesi

  • hooks.server.ts è l’unico punto che vede ogni richiesta: lì si legge il cookie e si riempie event.locals.
  • Il tipo di locals si dichiara in app.d.ts, altrimenti TypeScript vede un oggetto vuoto.
  • Il redirect per una pagina protetta va in +layout.server.ts del gruppo, non nel componente.

In SvelteKit l’autenticazione si aggancia a handle in src/hooks.server.ts: la funzione gira su ogni richiesta, legge il cookie di sessione e riempie event.locals, che load e form action leggono senza ripetere la query.

handle vede tutte le richieste

Il file si chiama hooks.server.ts e non ha equivalente lato client: nessun codice del browser può scrivere locals. Il tipo si dichiara in app.d.ts, altrimenti TypeScript vede un oggetto vuoto.

openssl rand -base64 32
// src/hooks.server.ts
import type { Handle } from "@sveltejs/kit";

export const handle: Handle = async ({ event, resolve }) => {
  const token = event.cookies.get("session");
  event.locals.user = token ? await readSession(token) : null;
  return resolve(event);
};

La dichiarazione dei tipi sta in src/app.d.ts e vale per tutto il progetto: senza, ogni lettura di locals.user è un errore di compilazione.

// src/app.d.ts
declare global {
  namespace App {
    interface Locals {
      user: { id: string; role: "admin" | "member" } | null;
    }
  }
}

export {};

Il controllo sta nel load del gruppo

Il redirect non va nel componente: va in un +layout.server.ts che copre il gruppo di pagine protette, così la regola vive in un file invece che in dieci.

// src/routes/(app)/+layout.server.ts
import { redirect } from "@sveltejs/kit";

export const load = ({ locals, url }) => {
  if (!locals.user) redirect(303, `/login?next=${url.pathname}`);
  return { user: locals.user };
};

Più hook, in ordine

sequence() compone più funzioni handle: la prima legge il cookie, la seconda registra la richiesta, la terza applica la regola. L’ordine è quello degli argomenti, quindi chi legge locals.user viene dopo chi lo scrive.

PuntoCosa vedeA cosa serve
hooks.server.tsOgni richiesta, anche API e fileLeggere il cookie e riempire locals
+layout.server.tsLe richieste del gruppoBloccare le pagine con redirect 303
+page.server.tsUna routeControllare un permesso sul singolo dato
Form actionIl POSTVerificare il ruolo prima di scrivere

Il limite da mettere in conto

Gli hook non girano per le pagine prerenderizzate: una pagina con export const prerender = true viene costruita una volta e non può controllare la sessione. E handle gira anche per gli asset, quindi va tenuto corto.

Errori che si vedono

  • Leggere la sessione nel componente con onMount: il primo HTML è già uscito senza utente.
  • Restituire una risposta da handle senza chiamare resolve: la pagina non viene mai resa.
  • Mettere la query del profilo in handle: gira anche sulla richiesta di un’immagine.
  • Salvare il token in localStorage: il server non lo vede e la pagina protetta resta raggiungibile con una richiesta diretta.

Le pagine che l’hook protegge si compongono da blocchi pronti: login e authentication coprono accesso e recupero, e flowbite-svelte fornisce i campi. Il caricamento dei dati è in load server vs universal, la validazione in form actions in SvelteKit, i file di rotta in routing e layout in SvelteKit, il confronto in Svelte vs React.

Domande frequenti

Posso proteggere solo alcune pagine?

Sì, mettendo il controllo in un +layout.server.ts dentro un gruppo di rotte, per esempio (app). Le pagine fuori dal gruppo non passano da quel load e restano pubbliche senza toccare l’hook.

Dove salvo la sessione?

In un cookie httpOnly, secure e sameSite lax, con un identificativo opaco e non i dati dell’utente. Il cookie viaggia su ogni richiesta, quindi deve essere piccolo e firmato.

Perché locals è vuoto in TypeScript?

Perché manca la dichiarazione in src/app.d.ts. Senza l’interfaccia App.Locals il compilatore non conosce i campi che handle scrive e li segnala come assenti.

Serve un hook per l’autorizzazione?

No, la lettura della sessione basta in un hook. Il permesso dipende dalla rotta e dal dato, quindi si controlla nel load o nell’azione che tocca quel dato, dove il contesto è noto.

Come funziona con più hook?

Con sequence(), che li esegue in ordine e passa il risultato del primo resolve al successivo. Senza sequence vince l’ultima esportazione handle, e le altre vengono ignorate.