Autenticazione
Panoramica
Sezione intitolata “Panoramica”Il modulo client.auth gestisce l’autenticazione degli utenti, la gestione dei token e la persistenza della sessione. Una volta effettuato l’accesso, tutte le richieste di dati successive includono automaticamente il JWT.
L’SDK memorizza 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, confirmationRequired } = await client.auth.signUp( "user@example.com", "password", "Jane Doe" // optional displayName);Con requireEmailVerification abilitato,
non c’è alcuna sessione finché l’indirizzo non viene confermato: confirmationRequired è true e user è null, quindi invita l’utente ad aprire l’email. Altrimenti confirmationRequired è false e user è l’account autenticato. Nella versione 0.23 signUp risolve { user, accessToken, refreshToken } ed effettua sempre l’accesso.
Provider OAuth
Sezione intitolata “Provider OAuth”L’SDK include metodi dedicati per i provider OAuth più diffusi, oltre a un generico signInWithOAuth() per qualsiasi provider personalizzato.
Supporta tre modalità di chiamata:
// 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”Ciascun 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);Twitter richiede anche il code verifier PKCE:
await client.auth.signInWithTwitter(code, redirectUri, codeVerifier);In un browser, signInWithApplePopup() apre il popup di Apple, controlla lo state ed
esegue l’accesso con il codice. Chiamalo da un gestore di click. Chiama preloadAppleSignIn()
al montaggio, poiché Safari blocca i popup in attesa del download di uno script:
import { preloadAppleSignIn } from "@rebasepro/client";
void preloadAppleSignIn(); // on mountawait client.auth.signInWithApplePopup({ clientId: "com.example.web" }); // on clickSenza clientId, l’SDK legge il Services ID da /auth/config, il che
ritarda il popup. L’URI di reindirizzamento predefinito è l’origine della pagina seguita da /, e
deve essere un Return URL sul Services ID (HTTPS, mai localhost). La chiusura del
popup genera un rifiuto con il codice APPLE_SIGN_IN_CANCELLED.
Un’app nativa iOS o macOS invia il codice di autorizzazione proveniente dall’accesso nativo
di Apple con il relativo bundle ID. Il bundle ID deve essere presente in APPLE_BUNDLE_IDS:
await client.auth.signInWithApple({ code: credential.authorizationCode, bundleId: "com.example.ios", user: { name: { firstName: "Jane" } } // sent by Apple on the first sign-in only});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 è sempre <base>/auth/magic-link?token=…, dove <base> corrisponde a email.magicLinkUrl del backend, oppure alla sua base per il ripristino della password (FRONTEND_URL nel runtime) quando non è impostato. Non è previsto un parametro redirectTo per singola richiesta. Gestisci tale percorso nel tuo frontend e restituisci il token per scambiarlo con una sessione. Il CMS lo gestisce automaticamente, effettuando l’accesso (tramite il passaggio del codice in presenza di un secondo fattore); gli altri frontend necessitano di una landing page.
// 1. Ask for the link.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 nello stesso modo indipendentemente dal fatto che l’indirizzo appartenga o meno a un account. È una scelta deliberata: un endpoint che rispondesse “utente inesistente” fungerebbe da oracolo per l’enumerazione degli account, quindi non utilizzare il risultato per comunicare a una persona se è registrata o meno — l’endpoint non lo sa.
L’invio richiede la configurazione di un servizio email sul backend, altrimenti
sendMagicLink risponderà con l’errore 503 EMAIL_NOT_CONFIGURED.
Codici monouso
Sezione intitolata “Codici monouso”Un codice a sei cifre inviato via email, ideale per le situazioni in cui un link risulterebbe poco pratico: 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 un tentativo di indovinare le sei cifre a un solo account, invece di consentire tentativi simultanei contro tutti gli account.
Sessioni anonime
Sezione intitolata “Sessioni anonime”Fai accedere un visitatore senza alcuna credenziale, consentendogli di iniziare a usare l’app prima ancora di avere un motivo per registrarsi:
const { user } = await client.auth.signInAnonymously();user.isAnonymous; // trueL’account è reale: ha un ID, dei ruoli e una sessione, quindi la row-level security ne delimita le righe esattamente come farebbe per un utente registrato. Ciò che non possiede è un modo per tornare indietro: nessuno può accedere come tale account una seconda volta, quindi tutto ciò che possiede va perso con la sessione.
linkAnonymous è il modo per evitare che sia usa e getta. L’utente mantiene il proprio ID, quindi
tutto ciò che ha creato mentre era 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, oppure l’ha disattivata dopo l’accesso di questo ospite: un ospite diventa un account solo finché allowAnonymous è attivo |
REGISTRATION_DISABLED (403) |
La registrazione è chiusa sul backend, e la conversione in un account equivale a una registrazione |
NOT_ANONYMOUS (400) |
La sessione corrente appartiene a un account ordinario |
EMAIL_EXISTS (409) |
L’indirizzo ha già un account associato — accedi a quell’account |
Un ospite che non torna più rimane memorizzato come riga nel database. Un amministratore può
rimuoverli con client.admin.listGuests(), client.admin.deleteGuest(uid) e
client.admin.deleteGuests({ lastSeenBefore }), oppure su Rebase Cloud con
rebase cloud guests. Questi comandi agiscono esclusivamente sugli ospiti,
e deleteGuests funziona a lotti: ripetilo finché remaining non è 0, impostando
offset sul numero di record saltati finora.
Collegamento di un provider a un account esistente
Sezione intitolata “Collegamento di un provider a un account esistente”signInWithGoogle e gli altri metodi simili eseguono l’accesso di un utente. linkProvider associa
l’identità di un provider all’account che ha già effettuato l’accesso, consentendo alla stessa persona di rientrare
utilizzando una qualsiasi delle due opzioni:
await client.auth.linkProvider("google", { idToken });La sessione dimostra già la proprietà dell’account, quindi, a differenza dell’accesso, questa operazione non
richiede che il provider abbia verificato l’email, e non è necessario che i due indirizzi
coincidano. Ha successo in modo idempotente (alreadyLinked: true) quando tale identità è
già associata a questo account, e rifiuta con IDENTITY_ALREADY_LINKED (409) quando
appartiene a un altro account.
Ricerca di un utente per email
Sezione intitolata “Ricerca di un utente per email”const profile = await client.auth.findUserByEmail("user@example.com");// { uid, displayName, photoURL } | nullTre campi non sensibili e nient’altro — quanto basta per mostrare “stai invitando
Jane” prima di inviare un invito. Disponibile solo su consenso esplicito: riservato a chiamanti autenticati e restituisce 404
a meno che il backend non imposti AUTH_ALLOW_USER_LOOKUP=true (auth.allowUserLookup).
Autenticazione a più fattori
Sezione intitolata “Autenticazione a più fattori”Registrazione di un fattore, secondo passaggio dell’accesso, codici di recupero e rimozione di un fattore: Autenticazione a più fattori.
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 } | nullNon conservare mai l’oggetto sessione. getSession() è un’istantanea. Ogni rinnovo
dell’access token, all’incirca una volta ogni ora, sostituisce la sessione con un nuovo
oggetto; pertanto, una sessione memorizzata al caricamento della pagina contiene un token che il server rifiuterà
una volta scaduto, facendo apparire l’app come disconnessa. Leggi la sessione al momento del bisogno.
Inviare il token alle tue route
Sezione intitolata “Inviare il token alle tue route”La chiamata a una funzione in backend/functions/ non richiede alcuna gestione manuale del token:
client.functions.invoke rinnova il token prima dell’invio ed effettua un nuovo tentativo in caso di errore 401.
const saved = await client.functions.invoke("progress", { level: 3 }, { path: "save" });Per qualsiasi altra richiesta, richiedi il token di volta in volta con getAccessToken().
Questo metodo rinnova un token scaduto o prossimo alla scadenza, attende il ripristino della sessione
al caricamento e restituisce null quando nessun utente ha effettuato l’accesso.
const token = await client.auth.getAccessToken();await fetch("https://other.example.com/upload", { method: "POST", headers: token ? { Authorization: `Bearer ${token}` } : {}, body: file});Ottenere 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();Una richiesta non viene mai inviata con un token che il client sa essere scaduto: le richieste condividono un unico aggiornamento e vengono inviate una sola volta, con il nuovo token. Un token rifiutato prematuramente dal server beneficia comunque di un aggiornamento, seguito da un nuovo tentativo per richiesta.
Dove risiede la sessione: authFlowMode
Sezione intitolata “Dove risiede la sessione: authFlowMode”const client = createRebaseClient({ baseUrl: API_URL, auth: { authFlowMode: "cookie" }});| Modalità | Dove risiede il refresh token | Predefinito |
|---|---|---|
"cookie" |
Un cookie HttpOnly impostato dal backend. Gli script in esecuzione sulla pagina non possono leggerlo, il che lo rende protetto da vulnerabilità XSS | Nel browser |
"json" |
Restituito nel corpo della risposta, memorizzato in storage (localStorage per impostazione predefinita) |
Al di fuori del browser (Node, worker, React Native) e nel browser quando passi un tuo storage personalizzato |
Le due parti devono concordare. La modalità cookie richiede auth.cookieAuth sul
backend, e il runtime di Rebase, utilizzato da Rebase Cloud, lo attiva sempre.
La modalità json richiede un backend privo di tale impostazione: a fronte di un backend con cookieAuth, a un
client json non viene fornito alcun refresh token, e la sua sessione termina alla scadenza dell’access token,
circa un’ora dopo l’accesso. Quando la risposta a un accesso evidenzia una mancata corrispondenza tra le
modalità, il client registra un errore indicando quale modalità utilizzare.
Il passaggio di uno storage personalizzato mantiene un client browser in modalità json. Questa configurazione è utile per un
secondo client sulla pagina che gestisce una propria sessione di accesso separata dal cookie della
pagina: in modalità cookie non verrebbe memorizzato nulla in tale storage.
Attesa del ripristino della sessione
Sezione intitolata “Attesa del ripristino della sessione”Una sessione ripristinata non è disponibile al primo render. getSession() è
sincrono, pertanto al caricamento della pagina restituisce null mentre il ripristino è ancora in
corso — e in modalità cookie un ripristino è sempre in corso, perché il refresh
token si trova in un cookie che la pagina non può leggere, obbligando il client a richiedere al server
un nuovo access token.
Leggerlo in modo sincrono è la causa del lampo di stato non autenticato (flash) 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 terminato il tentativo — indipendentemente dal fatto
che abbia trovato una sessione o meno:
async function currentUser() { await client.auth.isInitialized(); return client.auth.getSession()?.user ?? null;}In React, questo si traduce in un unico 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 gestisce già questo comportamento, quindi un’applicazione creata
sul template generato ne beneficia automaticamente. Le richieste di dati non devono attendere: una richiesta effettuata
durante il ripristino viene inviata non appena questo si stabilizza — come utente ripristinato oppure in modo anonimo — evitando
così che un primo render legga un elenco pubblico al posto dei dati riservati dell’utente.
Un ripristino che effettua un aggiornamento (un token memorizzato scaduto o la modalità cookie) raggiunge
onAuthStateChange come TOKEN_REFRESHED; un token memorizzato ancora valido viene adottato
in modo trasparente. Pertanto, un listener non può confermare l’avvenuto completamento del ripristino: un avvio che non emette
alcun evento è indistinguibile da uno ancora in corso. Attendi isInitialized()
per verificare questo stato e usa il listener per i cambiamenti successivi.
restoreOutcome() indica come si è concluso il ripristino, una volta che isInitialized() è stato
risolto: "restored", "none" (nulla da ripristinare), "refused" (il server ha
rifiutato la sessione) o "unreachable" (il server non è stato raggiungibile, quindi
la sessione non è stata né confermata né rifiutata). In modalità cookie, "unreachable"
in assenza di sessione rappresenta l’unico caso in cui non è certo che lo stato sia effettivamente “disconnesso”:
await client.auth.isInitialized();if (!client.auth.getSession() && client.auth.restoreOutcome() === "unreachable") { console.log("Offline: could not check the session.");}Listener dello stato di autenticazione
Sezione intitolata “Listener dello stato di autenticazione”Reagisci alle variazioni di autenticazione in tutta l’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 |
È stato completato un accesso o una registrazione |
TOKEN_REFRESHED |
L’access token è stato rinnovato — compreso il rinnovo trasparente che ripristina una sessione al caricamento della pagina |
USER_UPDATED |
updateUser() ha modificato il profilo, linkAnonymous() ha convertito un ospite in un account, changeEmail() ha avviato un cambio di indirizzo oppure confirmEmailChange() ha trasferito questo account al nuovo indirizzo |
SIGNED_OUT |
Una disconnessione, revokeAllSessions() o un aggiornamento del token fallito definitivamente |
Gestione della password
Sezione intitolata “Gestione della password”Password dimenticata
Sezione intitolata “Password dimenticata”const { success, message } = await client.auth.resetPasswordForEmail( "user@example.com");Ripristino della password (con token)
Sezione intitolata “Ripristino 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");Tutte le altre sessioni dell’account vengono terminate: chiunque ne possedesse una dovrà effettuare nuovamente l’accesso
con la nuova password. Questo dispositivo rimane autenticato. Il server risponde con una
nuova sessione e il client la adotta (emettendo TOKEN_REFRESHED).
Verifica dell’email
Sezione intitolata “Verifica dell’email”Al momento della registrazione, al nuovo account viene inviato il link di verifica se il servizio email è
configurato. sendVerificationEmail() lo invia nuovamente.
// Send verification email to the current userawait client.auth.sendVerificationEmail();
// Verify with the token from the email link. Signed in as that account, this// keeps everything on it.const { passwordRemoved } = await client.auth.verifyEmail(token);
// Signed out, an account that holds a password answers PROOF_REQUIRED:try { await client.auth.verifyEmail(token);} catch (e) { if (e instanceof Error && "code" in e && e.code === "PROOF_REQUIRED") { // Keep the password, and sign in: await client.auth.verifyEmail(token, { password }); // …or verify without it, which removes it: // await client.auth.verifyEmail(token, { removeUnproven: true }); }}Il link attesta l’accesso alla casella di posta, non l’identità di chi ha registrato l’indirizzo; pertanto, una password non convalidata né dalla sessione né dalla chiamata viene rimossa anziché conservata — consulta Verifica dell’email.
Su un backend con requireEmailVerification, signUp() restituisce
{ confirmationRequired: true, user: null } senza alcuna sessione: l’account
effettua l’accesso una volta aperto il link inserendo la propria password.
Modifica dell’indirizzo email
Sezione intitolata “Modifica dell’indirizzo email”Un utente autenticato trasferisce il proprio account a un altro indirizzo. Nulla cambia finché il nuovo indirizzo non conferma:
// Mails a link to the new address, and a notice to the current oneconst { pendingEmail, expiresAt } = await client.auth.changeEmail("jane@new.example");
// On the page the link opens (<frontend>/confirm-email-change?token=…):const { email, removedProviders } = await client.auth.confirmEmailChange(token);Il link ha una validità di 24 ore; getUser() segnala l’indirizzo in attesa come
pendingEmail. La conferma non richiede alcuna sessione e un client autenticato con quell’account
acquisisce il nuovo indirizzo ed emette USER_UPDATED. I motivi di rifiuto sono elencati
in Modifica di un indirizzo email.
Gestione della sessione (multi-dispositivo)
Sezione intitolata “Gestione della sessione (multi-dispositivo)”// List all active sessions, one per sign-inconst sessions = await client.auth.getSessions();// [{ id, userAgent, ipAddress, createdAt, isCurrentSession }, …]
// Revoke a specific sessionawait client.auth.revokeSession(sessionId);
// Revoke ALL sessions (logs out everywhere)await client.auth.revokeAllSessions();Ogni access token specifica la sessione di accesso a cui appartiene, pertanto esattamente una voce presenterà
isCurrentSession: true: quella del dispositivo che effettua la richiesta. La revoca di una sessione termina
contemporaneamente il refresh token e l’access token di quel dispositivo a partire dalla richiesta successiva,
sia su HTTP che su un socket realtime aperto. La disconnessione (signOut()) esegue la
stessa operazione per il dispositivo che si disconnette. Un access token emesso prima di questa versione
non specifica alcuna sessione; continuerà a funzionare fino alla sua scadenza, entro un’ora.
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, emailOtp?: boolean,// anonymousLogin: boolean, enabledProviders: string[],// providerClientIds?: Record<string, string>, // public ids, e.g. { apple: Services ID }// externalLoginUrl?: string, needsSetup?: boolean, storage?: boolean// }Archiviazione personalizzata della sessione
Sezione intitolata “Archiviazione personalizzata 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 }});Utilizzo di un proprio provider di autenticazione
Sezione intitolata “Utilizzo di un proprio provider di autenticazione”Clerk, Firebase Auth o una sessione già esistente nell’applicazione, con un getter di token
al posto di client.auth: Un proprio provider di autenticazione.
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; pendingEmail?: string | null; // an address change waiting for its link 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 realtime — Dati in tempo reale con WebSocket
- Backend di autenticazione — Configurazione dell’autenticazione lato server