Autenticazione
Panoramica
Sezione intitolata “Panoramica”Il modulo client.auth gestisce l’autenticazione degli utenti, la gestione dei token e la persistenza delle sessioni. Una volta che un utente ha effettuato l’accesso, tutte le richieste di dati successive includono automaticamente il JWT.
L’SDK rende persistenti le sessioni in localStorage per impostazione predefinita e aggiorna automaticamente i token prima della loro scadenza.
Email / Password
Sezione intitolata “Email / Password”Accesso
Sezione intitolata “Accesso”const { user, accessToken, refreshToken } = await client.auth.signInWithEmail( "user@example.com", "password");console.log(user.uid, user.email);Registrazione
Sezione intitolata “Registrazione”const { user } = await client.auth.signUp( "user@example.com", "password", "Jane Doe" // optional displayName);Provider OAuth
Sezione intitolata “Provider OAuth”L’SDK include metodi dedicati per i provider OAuth più diffusi, oltre a un metodo generico signInWithOAuth() per qualsiasi provider personalizzato.
Supporta tre stili di invocazione:
// ID-token flow (One Tap / Sign In With Google button)await client.auth.signInWithGoogle({ idToken: googleIdToken });
// Access-token flow (popup)await client.auth.signInWithGoogle({ accessToken: googleAccessToken });
// Authorization code flow (most secure, server-side exchange)await client.auth.signInWithGoogle({ code: authCode, redirectUri: "https://..." });Altri provider
Sezione intitolata “Altri provider”Ogni provider segue il flusso del codice di autorizzazione con (code, redirectUri):
await client.auth.signInWithGitHub(code, redirectUri);await client.auth.signInWithMicrosoft(code, redirectUri);await client.auth.signInWithFacebook(code, redirectUri);await client.auth.signInWithLinkedin(code, redirectUri);await client.auth.signInWithDiscord(code, redirectUri);await client.auth.signInWithGitLab(code, redirectUri);await client.auth.signInWithBitbucket(code, redirectUri);await client.auth.signInWithSlack(code, redirectUri);await client.auth.signInWithSpotify(code, redirectUri);Apple e Twitter richiedono parametri aggiuntivi:
// Apple — optional user info from first sign-inawait client.auth.signInWithApple(code, redirectUri, { name: { firstName: "Jane", lastName: "Doe" }, email: "jane@example.com"});
// Twitter — requires PKCE code verifierawait client.auth.signInWithTwitter(code, redirectUri, codeVerifier);OAuth generico
Sezione intitolata “OAuth generico”Per qualsiasi provider registrato sul backend:
await client.auth.signInWithOAuth("custom-provider", { code: authCode, redirectUri: "https://myapp.com/callback"});Magic Link
Sezione intitolata “Magic Link”Un link di accesso con un clic inviato via email. Il link reindirizza a una tua pagina che trasporta un token; restituisci il token per scambiarlo con una sessione.
// 1. Ask for the link. `redirectTo` is where the link points.await client.auth.sendMagicLink("user@example.com");
// 2. On the landing page, trade the token for a session.const token = new URLSearchParams(location.search).get("token")!;const { user } = await client.auth.verifyMagicLink(token);sendMagicLink risponde allo stesso modo indipendentemente dal fatto che l’indirizzo abbia o meno un account. Questo è intenzionale: un endpoint che rispondesse “nessun utente trovato” costituirebbe un oracolo per l’enumerazione degli account, quindi non usare il risultato per comunicare a una persona se è registrata o meno — non è possibile saperlo.
Entrambi richiedono un servizio email configurato sul backend, altrimenti restituiscono 503 EMAIL_NOT_CONFIGURED.
Codici monouso
Sezione intitolata “Codici monouso”Un codice a sei cifre inviato via email, ideale per i casi in cui l’uso di un link risulta disagevole — un’app nativa, un secondo dispositivo, un browser che altera i link.
const { expiresInSeconds } = await client.auth.sendEmailOtp("user@example.com");
// The address goes back with the code, because the code is only valid for it.const { user } = await client.auth.verifyEmailOtp("user@example.com", "418293");L’invio dell’indirizzo insieme al codice è ciò che limita il tentativo di indovinare le sei cifre a un solo account, anziché a tutti gli account contemporaneamente.
Sessioni anonime
Sezione intitolata “Sessioni anonime”Consente a un visitatore di accedere senza credenziali, così da poter iniziare a utilizzare l’app prima ancora di avere un motivo per registrarsi:
const { user } = await client.auth.signInAnonymously();user.isAnonymous; // trueL’account è reale: possiede un ID, ruoli e una sessione, quindi la sicurezza a livello di riga (row-level security) limita l’accesso alle sue righe esattamente come farebbe per un utente registrato. Ciò che non possiede è un modo per tornare indietro — nessuno può accedere nuovamente con quell’identità, quindi tutto ciò che possiede va perso con la sessione.
linkAnonymous è il modo per non renderlo usa e getta. L’utente mantiene il proprio ID, quindi tutto ciò che ha creato da anonimo rimane di sua proprietà:
await client.auth.linkAnonymous("user@example.com", "correct-horse-battery");| Errore | Significato |
|---|---|
ANONYMOUS_AUTH_DISABLED (403) |
Il backend non ha abilitato l’autenticazione anonima |
NOT_ANONYMOUS (400) |
La sessione corrente appartiene a un account ordinario |
EMAIL_EXISTS (409) |
L’indirizzo ha già un account — accedi invece a quello |
Collegamento di un provider a un account esistente
Sezione intitolata “Collegamento di un provider a un account esistente”signInWithGoogle e metodi simili fanno accedere un utente. linkProvider associa un’identità del provider all’account attualmente connesso, in modo che la stessa persona possa rientrare da entrambi gli accessi:
await client.auth.linkProvider("google", { idToken });La sessione dimostra già la proprietà dell’account, quindi, a differenza dell’accesso, questo non richiede che il provider abbia verificato l’email e i due indirizzi non devono necessariamente coincidere. Ha successo in modo idempotente (alreadyLinked: true) quando quell’identità è già associata a questo account, e rifiuta con IDENTITY_ALREADY_LINKED (409) quando appartiene a un account diverso.
Ricerca di un utente tramite email
Sezione intitolata “Ricerca di un utente tramite email”const profile = await client.auth.findUserByEmail("user@example.com");// { uid, displayName, photoURL } | nullTre campi non sensibili e nient’altro — sufficienti per mostrare “stai invitando Jane” prima che venga inviato un invito.
Autenticazione a più fattori (MFA)
Sezione intitolata “Autenticazione a più fattori (MFA)”Fattori TOTP — un’app di autenticazione — più la richiesta di verifica (challenge) che eleva una sessione da aal1 a aal2.
Registrazione di un fattore
Sezione intitolata “Registrazione di un fattore”const { factor, totp, recoveryCodes } = await client.auth.mfa.enroll({ friendlyName: "Phone"});
showQrCode(totp.uri); // otpauth://… — what the authenticator scansshowRecoveryCodes(recoveryCodes);Mostra i codici di recupero una sola volta e mai più. Vengono memorizzati solo i relativi hash, quindi nulla potrà mostrarli in seguito.
Il fattore non è utilizzabile finché l’utente non dimostra che il proprio autenticatore ha generato un codice a partire da quel segreto:
await client.auth.mfa.verify(factor.id, "418293");Accesso con MFA
Sezione intitolata “Accesso con MFA”L’accesso a un account con MFA registrata restituisce una sessione a livello aal1. Apri un challenge e rispondi per ottenere quella effettiva:
const factors = await client.auth.mfa.listFactors();const { challengeId } = await client.auth.mfa.challenge(factors[0].id);
// A TOTP code, or one of the recovery codes.const { user } = await client.auth.mfa.verifyChallenge(challengeId, "418293");verifyChallenge genera la sessione aal2 e questo client la adotta, sostituendo i token restituiti all’accesso. Un challenge scade dopo cinque minuti e un challenge che ha raggiunto il limite massimo di tentativi rimane esaurito per il resto della sua validità — altrimenti un challenge aperto consentirebbe tentativi illimitati di indovinare le sei cifre.
Rimozione di un fattore
Sezione intitolata “Rimozione di un fattore”await client.auth.mfa.unenroll(factorId);Richiede una sessione aal2 — ovvero una sessione che ha già risposto a un challenge — in modo che un token aal1 sottratto non possa disattivare l’MFA. La rimozione dell’ultimo fattore verificato elimina anche i codici di recupero.
Disconnessione
Sezione intitolata “Disconnessione”await client.auth.signOut();Questo revoca il refresh token sul server, cancella la sessione locale ed emette un evento SIGNED_OUT.
Gestione della sessione
Sezione intitolata “Gestione della sessione”Ottenere la sessione corrente
Sezione intitolata “Ottenere la sessione corrente”const session = client.auth.getSession();// { accessToken, refreshToken, expiresAt, user } | nullOttenere l’utente corrente (verificato dal server)
Sezione intitolata “Ottenere l’utente corrente (verificato dal server)”const user = await client.auth.getUser();// Fetches the user from the backend (GET /auth/me)Aggiornare il profilo utente
Sezione intitolata “Aggiornare il profilo utente”const updatedUser = await client.auth.updateUser({ displayName: "Jane Doe", photoURL: "https://example.com/avatar.jpg"});Aggiornare il token
Sezione intitolata “Aggiornare il token”L’aggiornamento del token avviene automaticamente, ma puoi avviarlo manualmente:
const session = await client.auth.refreshSession();Dove risiede la sessione: authFlowMode
Sezione intitolata “Dove risiede la sessione: authFlowMode”const client = createRebaseClient({ baseUrl: API_URL, auth: { authFlowMode: "cookie" }});| Modalità | Dove si trova il refresh token | Quando usarla |
|---|---|---|
"json" (predefinito) |
Restituito nel corpo della risposta, conservato in localStorage |
Un’app nativa, uno script, qualsiasi contesto privo del gestore di cookie di un browser |
"cookie" |
Un cookie HttpOnly impostato dal backend | Un’applicazione browser. Lo script in esecuzione sulla pagina non può leggerlo, il che lo rende sicuro contro gli attacchi XSS |
La modalità cookie richiede auth.cookieAuth sul backend ed è quella utilizzata dal template frontend generato.
Attesa del ripristino della sessione
Sezione intitolata “Attesa del ripristino della sessione”Una sessione ripristinata non è disponibile al primo rendering. getSession() è sincrono, quindi al caricamento della pagina restituisce null mentre il ripristino è ancora in corso — e in modalità cookie un ripristino è sempre in corso, poiché il refresh token si trova in un cookie che la pagina non può leggere, costringendo il client a richiedere al server un nuovo access token.
Leggerlo in modo sincrono è ciò che produce un flash di stato disconnesso a ogni ricaricamento:
// Wrong: renders the signed-out view for one round trip, every reload.const session = client.auth.getSession();if (!session) return <SignIn />;isInitialized() si risolve una volta che il client ha completato il tentativo — sia che abbia trovato una sessione o meno:
async function currentUser() { await client.auth.isInitialized(); return client.auth.getSession()?.user ?? null;}In React, questo corrisponde a un singolo effetto:
import { useEffect, useState } from "react";
function useCurrentUser() { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true);
useEffect(() => { let cancelled = false; client.auth.isInitialized().then(() => { if (cancelled) return; setUser(client.auth.getSession()?.user ?? null); setLoading(false); }); return () => { cancelled = true; }; }, []);
return { user, loading };}useRebaseAuthController in @rebasepro/app esegue già questa operazione, quindi un’applicazione creata sul template generato ne dispone automaticamente.
Un ripristino riuscito raggiunge anche onAuthStateChange come TOKEN_REFRESHED — è a tutti gli effetti un refresh — ma un listener da solo non può indicare che il ripristino è terminato: un avvio senza sessione non emette alcunché, risultando indistinguibile da un avvio ancora in corso. Attendi isInitialized() per verificare questo stato e usa il listener per le modifiche successive.
Listener dello stato di autenticazione
Sezione intitolata “Listener dello stato di autenticazione”Reagisci ai cambiamenti di autenticazione all’interno dell’applicazione:
const unsubscribe = client.auth.onAuthStateChange((event, session) => { // event: "SIGNED_IN" | "SIGNED_OUT" | "TOKEN_REFRESHED" | "USER_UPDATED" console.log("Auth event:", event); console.log("Session:", session?.user?.email);});
// Stop listeningunsubscribe();| Evento | Quando |
|---|---|
SIGNED_IN |
Un accesso o una registrazione sono stati completati |
TOKEN_REFRESHED |
L’access token è stato rinnovato — compreso il rinnovo silenzioso che ripristina una sessione al caricamento della pagina |
USER_UPDATED |
updateUser() ha modificato il profilo |
SIGNED_OUT |
Una disconnessione, o un aggiornamento del token fallito definitivamente |
Gestione delle password
Sezione intitolata “Gestione delle password”Password dimenticata
Sezione intitolata “Password dimenticata”const { success, message } = await client.auth.resetPasswordForEmail( "user@example.com");Reimpostazione della password (con token)
Sezione intitolata “Reimpostazione della password (con token)”const { success, message } = await client.auth.resetPassword( resetToken, "newSecurePassword");Modifica della password (autenticato)
Sezione intitolata “Modifica della password (autenticato)”const { success, message } = await client.auth.changePassword( "oldPassword", "newPassword");Verifica dell’email
Sezione intitolata “Verifica dell’email”// Send verification email to the current userawait client.auth.sendVerificationEmail();
// Verify with the token from the email linkawait client.auth.verifyEmail(token);Gestione delle sessioni (multi-dispositivo)
Sezione intitolata “Gestione delle sessioni (multi-dispositivo)”// List all active sessionsconst sessions = await client.auth.getSessions();
// Revoke a specific sessionawait client.auth.revokeSession(sessionId);
// Revoke ALL sessions (logs out everywhere)await client.auth.revokeAllSessions();Configurazione dell’autenticazione
Sezione intitolata “Configurazione dell’autenticazione”Interroga la configurazione di autenticazione del backend:
const config = await client.auth.getAuthConfig();// {// hasBuiltInAuthRoutes: boolean,// emailPasswordLogin: boolean,// registrationEnabled: boolean, // open right now, bootstrap window included// passwordReset: boolean, // needs an email service// adminPasswordReset: boolean,// sessionManagement: boolean,// profileUpdate: boolean,// emailVerification: boolean,// magicLink: boolean,// anonymousLogin: boolean,// enabledProviders: string[],// needsSetup: boolean// }Storage personalizzato della sessione
Sezione intitolata “Storage personalizzato della sessione”Per impostazione predefinita, le sessioni sono memorizzate in localStorage. Puoi personalizzare questo comportamento con l’opzione auth:
import { createRebaseClient, createCookieStorage } from "@rebasepro/client";
// Use cookies instead of localStorageconst client = createRebaseClient({ baseUrl: import.meta.env.VITE_API_URL, auth: { storage: createCookieStorage({ path: "/", sameSite: "Lax", secure: true }), autoRefresh: true, // default: true persistSession: true // default: true }});Struttura dell’oggetto User
Sezione intitolata “Struttura dell’oggetto User”// Canonical type — import from @rebasepro/typesinterface User { uid: string; email: string | null; displayName: string | null; photoURL: string | null; providerId: string; isAnonymous: boolean; emailVerified?: boolean; roles?: string[]; // text[] from the users table metadata?: Record<string, unknown>;}Passaggi successivi
Sezione intitolata “Passaggi successivi”- Interrogazione dei dati — Operazioni CRUD e query builder
- Sottoscrizioni in tempo reale — Dati in tempo reale con WebSocket
- Backend di autenticazione — Configurazione dell’autenticazione lato server