React

Middleware per l’autenticazione in Next.js

Il middleware filtra le richieste, non autorizza: come leggere il cookie, verificare il token e proteggere anche le Server Actions.

In sintesi

  • Il middleware filtra, la pagina autorizza: il permesso si verifica dove il dato viene letto.
  • La presenza di un cookie non prova niente: la firma del token si controlla sul server.
  • Ogni controllo nel middleware gira su ogni richiesta: tienilo locale e veloce.

Il middleware legge il cookie di sessione e reindirizza prima che la pagina venga renderizzata, ma non è un controllo di autorizzazione. Il permesso si verifica nella pagina o nell’azione che legge il dato.

Cosa può fare il middleware

  • Leggere cookie, header e URL, e decidere se lasciar passare la richiesta.
  • Riscrivere il percorso, redirigere e aggiungere header alla risposta.
  • Girare prima del rendering, quindi anche sulle route che non esistono.
  • Non interrogare un database con un driver Node e non conoscere i permessi aggiornati al millisecondo.
npm install jose

Un matcher, poi il redirect

// middleware.ts
import { NextResponse, type NextRequest } from "next/server";

const PUBLIC = ["/login", "/signup"];

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  if (PUBLIC.some((path) => pathname.startsWith(path))) return NextResponse.next();

  if (!request.cookies.get("session")) {
    const url = request.nextUrl.clone();
    url.pathname = "/login";
    url.searchParams.set("next", pathname);
    return NextResponse.redirect(url);
  }
  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

Il matcher è parte della protezione: senza esclusione degli asset, il middleware gira su ogni immagine e su ogni file CSS. Next.js 16 ha rinominato il file in proxy.ts: la funzione e il matcher si comportano allo stesso modo, cambia il nome.

Un cookie si crea dal browser in dieci secondi. Il middleware qui controlla solo che il cookie esista, e questo va bene come prima scrematura: chi non ha il cookie non arriva alla pagina. La firma del token, invece, va verificata sul server, con una chiave che il client non possiede.

// lib/session.ts
import { jwtVerify } from "jose";

const secret = new TextEncoder().encode(process.env.SESSION_SECRET);

export async function verifySession(token?: string) {
  if (!token) return null;
  try {
    const { payload } = await jwtVerify(token, secret);
    return payload as { userId: string; role: string };
  } catch {
    return null;
  }
}

Verifica dove il dato viene letto

La pagina non si fida del fatto che il middleware sia passato: ripete la verifica e filtra la query per l’utente della sessione. Se domani qualcuno cambia il matcher, il controllo resta. Questa è la differenza tra un cancello e una recinzione.

// app/dashboard/page.tsx
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { verifySession } from "@/lib/session";

export default async function DashboardPage() {
  const session = await verifySession((await cookies()).get("session")?.value);
  if (!session) redirect("/login");

  const invoices = await db.invoice.findMany({ where: { userId: session.userId } });
  return ;
}

Il costo di girare su tutto

Il middleware esegue su ogni richiesta che corrisponde al matcher, prima che la route esista. Una fetch esterna o una query al database lì dentro rallenta ogni pagina del sito, comprese quelle pubbliche. Tieni il controllo locale: un cookie, un header, un confronto di stringhe.

Un blocco di login non è un sistema di sessioni

I blocchi di login e di autenticazione danno form, stati e markup, non la gestione del token. Il record Sign In mostra la parte visibile di un flusso completo. La sessione la costruisci tu: firma, scadenza, revoca.

Errori che si vedono in produzione

  • Proteggere le pagine con il middleware e lasciare le Server Actions aperte: l’azione si chiama anche senza passare dalla pagina.
  • Mettere la query dell’utente nel middleware: il tempo di risposta di ogni pagina dipende dal database.
  • Dimenticare /login tra i percorsi pubblici: il redirect punta a una pagina che redirige.
  • Trattare il ruolo scritto nel cookie come definitivo: il cookie è del client finché non lo verifichi.

Un limite da dichiarare

Il middleware non revoca una sessione già emessa. Un token valido resta valido fino alla scadenza, anche dopo un logout o un cambio di password, a meno che la pagina non consulti una lista di revoca. Tieni la scadenza corta e la revoca nel punto che legge il dato.

I blocchi di accesso già pronti sono confrontati in i migliori blocchi di autenticazione; il confine tra server e client component è spiegato in server component che importa un hook. Il confronto 21st.dev vs shadcnblocks aiuta a scegliere da dove copiare il form.

Domande frequenti

Il middleware basta per proteggere una pagina?

No. Filtra le richieste senza cookie, ma la verifica del token e del permesso deve avvenire nella pagina o nell’azione che legge il dato.

Perché il middleware non può leggere il database?

Perché gira su un runtime leggero, senza driver Node. Una query lì dentro rallenta ogni richiesta e spesso non è nemmeno disponibile.

Come evito un loop di redirect sul login?

Inserisci il login e le altre pagine pubbliche in una lista esclusa, controllata prima del redirect.

Devo proteggere anche le Server Actions?

Sì. Un’azione è un endpoint POST: chi la chiama non passa dal middleware della pagina, quindi ripeti il controllo dentro l’azione.

Middleware o proxy in Next.js 16?

Sono la stessa cosa: il file è stato rinominato proxy.ts, la funzione e il matcher non cambiano.