Docs/Feature
// feature
Login con Google
Il login con Google è già cablato in ZeroToShip e passa da Supabase Auth. Non c'è nessuna libreria OAuth di terze parti da installare: il bottone chiama signInWithOAuth, Supabase gestisce il giro su Google, e una route handler nel repo scambia il codice per una session. La parte di codice è già scritta — quello che devi fare tu è la configurazione su Google Cloud e su Supabase.
Il bottone in app/signin/page.tsx
La pagina /signin è un Client Component con una sola funzione, handleSignup, che gestisce sia OAuth che il magic link. Il bottone Google la chiama con type: “oauth” e provider: “google”:
const supabase = createClient(); // libs/supabase/client.ts
const redirectURL = window.location.origin + "/api/auth/callback";
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: redirectURL,
},
});Nota il redirectTo: è sempre l'origin corrente più /api/auth/callback. In locale diventa http://localhost:3000/api/auth/callback, in produzione https://tuodominio.com/api/auth/callback. Entrambi vanno messi nella allowlist di Supabase (sotto).
Il callback che crea la session
Google rimanda l'utente a Supabase, Supabase rimanda al tuo /api/auth/callback con un ?code=... in query string. La route handler fa una cosa sola: scambia quel codice per una session e ti sposta sul callbackUrl del config.
export const dynamic = "force-dynamic";
export async function GET(req: NextRequest) {
const requestUrl = new URL(req.url);
const code = requestUrl.searchParams.get("code");
if (code) {
const supabase = await createClient();
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(requestUrl.origin + config.auth.callbackUrl);
}Il callbackUrl di default è /dashboard — lo cambi in config.ts sotto auth. Attenzione a un dettaglio: la route ignora qualsiasi altro query param. components/ButtonGithubSignin.tsx passa un ?next= nell'URL di callback, ma nessuno lo legge: l'utente finisce comunque su config.auth.callbackUrl. Se ti serve tornare alla pagina di partenza, quel supporto va aggiunto a mano:
// Non è ancora nel repo: aggiungilo se ti serve il ritorno alla pagina di partenza
const next = requestUrl.searchParams.get("next");
const destination = next?.startsWith("/") ? next : config.auth.callbackUrl;
return NextResponse.redirect(requestUrl.origin + destination);La session nel middleware
Dopo il login la session vive nei cookie. A tenerla fresca ci pensa il middleware in root: middleware.ts chiama updateSession di libs/supabase/middleware.ts, che ricrea un client server-side, legge e riscrive i cookie e fa un supabase.auth.getUser() a ogni richiesta — è quella chiamata che rinnova il token scaduto.
export async function middleware(request: NextRequest) {
return await updateSession(request);
}
export const config = {
matcher: [
"/((?!_next/static|_next/image|favicon.ico|.*\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
],
};Il matcher gira su tutto tranne asset statici e immagini. Non devi toccarlo per far funzionare Google — è già così.
Setup su Google Cloud
- Vai su console.cloud.google.com e crea un progetto (o usane uno esistente).
- APIs & Services → OAuth consent screen: tipo External, nome app, email di supporto, dominio. In modalità Testing solo gli account che aggiungi come test user possono entrare — quando sei pronto, pubblica.
- Credentials → Create Credentials → OAuth client ID, tipo Web application.
- Authorized JavaScript origins:
http://localhost:3000ehttps://tuodominio.com. - Authorized redirect URIs: qui va l'URL di Supabase, non il tuo. La forma esatta è
https://<project-ref>.supabase.co/auth/v1/callback. Ilproject-refè la parte prima di.supabase.conella tuaNEXT_PUBLIC_SUPABASE_URL. - Copia Client ID e Client Secret.
Errore numero uno di tutti: mettere http://localhost:3000/api/auth/callback tra i redirect URI di Google. Non è quello — Google parla con Supabase, Supabase parla con te.
Setup su Supabase
- Dashboard Supabase → Authentication → Providers → Google: attiva il toggle, incolla Client ID e Client Secret, salva.
- Authentication → URL Configuration: metti la Site URL (in dev
http://localhost:3000, in produzione il tuo dominio) e aggiungi ai Redirect URLs entrambi gli indirizzi di callback:http://localhost:3000/api/auth/callbackehttps://tuodominio.com/api/auth/callback. - Verifica che in
.env.localci sianoNEXT_PUBLIC_SUPABASE_URLeNEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY— sono le due chiavi lette dalibs/supabase/client.ts. Dettagli in Variabili d'ambiente.
Se un redirect non è nella allowlist, Supabase ti sbatte sulla Site URL senza codice e sembra che il login “non faccia niente”. Novanta volte su cento il bug è lì.
Il bottone riutilizzabile
components/ButtonSignin.tsx non fa OAuth: legge l'utente con supabase.auth.getUser() e mostra l'avatar (da user_metadata.avatar_url, che Google popola da solo) con link a config.auth.callbackUrl, oppure un link a config.auth.loginUrl se non sei loggato. Mettilo nell'header e sei a posto.
Nel repo c'è un solo bottone OAuth stand-alone, ButtonGithubSignin.tsx. La versione Google non c'è: se ti serve fuori dalla pagina /signin, copia quel file e cambia il provider.
// Non è ancora nel repo: copia ButtonGithubSignin.tsx e cambia provider
await supabase.auth.signInWithOAuth({
provider: "google",
options: { redirectTo: window.location.origin + "/api/auth/callback" },
});Come proteggere le pagine una volta che l'utente è dentro: Autenticazione.