Endpoint e token di autenticazione
Le route montate dal blocco auth e i token che restituiscono.
Endpoint di autenticazione
Sezione intitolata “Endpoint di autenticazione”Tutti gli endpoint di autenticazione sono montati su /api/auth/:
| Metodo | Percorso | Descrizione |
|---|---|---|
POST |
/api/auth/register |
Crea un nuovo account |
POST |
/api/auth/login |
Accedi con email/password |
POST |
/api/auth/refresh |
Rinnova l’access token |
POST |
/api/auth/<provider> |
Accesso OAuth (ad es. /api/auth/google, /api/auth/linkedin) |
POST |
/api/auth/link/<provider> |
Collega un provider OAuth all’account autenticato. Su un guest questa è una registrazione: richiede allowRegistration, prende l’indirizzo del provider quando il provider lo garantisce, e risponde con una sessione per l’account che il guest è diventato |
POST |
/api/auth/logout |
Revoca il refresh token |
POST |
/api/auth/forgot-password |
Invia l’email per reimpostare la password |
POST |
/api/auth/reset-password |
Reimposta la password con un token |
POST |
/api/auth/find-user |
Risolve un’email in un profilo pubblico minimo (opzionale — AUTH_ALLOW_USER_LOOKUP) |
POST |
/api/auth/change-password |
Modifica la password del chiamante (autenticato). Termina ogni altra sessione e risponde con una nuova per il chiamante |
GET |
/api/auth/me |
Il profilo del chiamante |
PATCH |
/api/auth/me |
Aggiorna il profilo del chiamante |
POST |
/api/auth/change-email |
{ newEmail }: sposta l’account del chiamante su un altro indirizzo. Invia via email un link al nuovo indirizzo e un avviso a quello vecchio; nulla cambia finché il link non viene seguito. Richiede aal2 su un account con un secondo fattore. 409 EMAIL_EXISTS oppure UNDELIVERABLE_ADDRESS, 400 EMAIL_UNCHANGED, 403 ANONYMOUS_USER per un guest — vedi Cambiare un indirizzo email |
POST |
/api/auth/confirm-email-change |
{ token } dal link. Non richiede alcuna sessione. Sposta l’account sul nuovo indirizzo, verificato; 400 INVALID_TOKEN per un link consumato, sostituito o scaduto, 409 EMAIL_EXISTS quando l’indirizzo è stato preso mentre era in attesa |
GET |
/api/auth/config |
Ciò che questo backend offre a una schermata di accesso — needsSetup, registrationEnabled, passwordReset, emailVerification, magicLink, anonymousLogin, adminPasswordReset, enabledProviders, e storage, che indica se il backend serve lo storage dei file. Non autenticato e calcolato a partire dagli stessi predicati applicati dalle route, garantendo che ciò che la schermata mostra corrisponda esattamente a ciò che può fare |
POST |
/api/auth/send-verification |
Invia al chiamante un link di verifica dell’email |
GET |
/api/auth/verify-email |
Consuma un link di verifica (l’URL contenuto nell’email). Mantiene ciò che una sessione attiva dell’account dimostra e rimuove ciò che nessuno ha dimostrato — vedi Verifica email |
POST |
/api/auth/verify-email |
Lo stesso con { token, password?, removeUnproven? }: la password lo mantiene e accede; senza nessuna delle due prove un account che ne possiede una risponde 409 PROOF_REQUIRED |
POST |
/api/auth/magic-link |
Invia via email un link di accesso monouso. 503 EMAIL_NOT_CONFIGURED se SMTP non è configurato |
POST |
/api/auth/magic-link/verify |
Scambia un token magic-link con una sessione |
POST |
/api/auth/otp |
Invia via email un codice di accesso a sei cifre. Risponde allo stesso modo indipendentemente dal fatto che l’indirizzo sia associato a un account o meno |
POST |
/api/auth/otp/verify |
Scambia { email, code } con una sessione |
POST |
/api/auth/anonymous |
Crea una sessione anonima (opzionale — ALLOW_ANONYMOUS) |
POST |
/api/auth/anonymous/link |
Associa credenziali reali all’account anonimo già autenticato. Richiede sia allowAnonymous sia la registrazione aperta: 403 ANONYMOUS_AUTH_DISABLED quando l’accesso anonimo è disattivato, anche per i guest già autenticati, e 403 REGISTRATION_DISABLED finché la registrazione è chiusa |
GET |
/api/auth/sessions |
Elenca le sessioni attive del chiamante, una per accesso. Quella del chiamante stesso è contrassegnata isCurrentSession |
DELETE |
/api/auth/sessions |
Revoca tutte le sessioni, inclusa quella corrente — disconnessione remota su tutti i dispositivi |
DELETE |
/api/auth/sessions/:id |
Revoca una sessione: il suo refresh token, e l’access token posseduto da quel dispositivo |
GET |
/api/auth/scopes |
Ogni scope noto a questo backend, e quelli che possiede il chiamante |
GET |
/api/auth/keys |
Le chiavi API personali del chiamante |
POST |
/api/auth/keys |
Crea una chiave personale. 403 PERSONAL_KEYS_DISABLED a meno che la collezione users non imposti auth.personalKeys |
DELETE |
/api/auth/keys/:id |
Revoca una delle chiavi del chiamante |
GET |
/.well-known/jwks.json |
Il JWKS pubblico — montato alla radice, non sotto basePath, perché è lì che cerca un verificatore. Presente quando la firma asimmetrica è configurata |
POST |
/api/auth/mfa/enroll |
Avvia la registrazione TOTP (restituisce il segreto e i codici di recupero) |
POST |
/api/auth/mfa/verify |
Conferma la registrazione con un codice proveniente dall’app di autenticazione |
GET |
/api/auth/mfa/factors |
Elenca i fattori registrati dal chiamante |
POST |
/api/auth/mfa/challenge |
Avvia una verifica (challenge) su un fattore verificato |
POST |
/api/auth/mfa/challenge/verify |
Risponde a una challenge — questo passaggio emette la sessione |
DELETE |
/api/auth/mfa/unenroll |
Rimuove un fattore (richiede una sessione aal2 una volta che l’account ha un fattore verificato) |
POST |
/api/auth/mfa/recovery-codes |
Sostituisce i codici di recupero del chiamante con dieci nuovi (richiede una sessione aal2) |
La gestione amministrativa di utenti e ruoli è una superficie separata, montata su
/api/admin/ anziché /api/auth/. La lettura richiede lo scope users:read e
la modifica richiede users:write, tranne la lettura ristretta del selettore utenti qui sotto. Un admin e la chiave di servizio (service key) li possiedono entrambi; li possiede
anche un ruolo che li dichiara. Nessuno può modificare un account che possiede più di quanto possieda
lui. Vedi Ruoli e scope.
| Metodo | Percorso | Descrizione |
|---|---|---|
GET |
/api/admin/users |
Elenca gli utenti (con paginazione). ?search= trova una parte dell’email o del nome visualizzato, senza distinzione tra maiuscole e minuscole. Su Postgres trova anche qualsiasi ruolo che l’utente possiede, e l’uid; su MongoDB trova ancora solo email e nome visualizzato |
GET |
/api/admin/users/pick |
La lettura del selettore utenti: uid, displayName, email e photoURL per ogni utente, nient’altro. Accetta ?search=, ?limit= (25 per impostazione predefinita) e ?offset=, oppure ?ids=a,b per gli utenti che un valore memorizzato nomina. Risponde a users:read, e anche a chiunque possa scrivere una collection con un campo userSelect, così un editor può compilarlo. Una regola scritta come SQL grezzo (using / withCheck che il modello di policy non sa leggere) non può essere valutata in anticipo, quindi qui conta come un rifiuto: su una collection di questo tipo possono scegliere solo i titolari di users:read. Chiunque altro riceve 403 SCOPE_MISSING |
POST |
/api/admin/users |
Crea un utente |
GET |
/api/admin/users/:uid |
Legge un singolo utente |
PUT |
/api/admin/users/:uid |
Aggiorna un singolo utente. { disabled: true } disattiva l’account senza eliminarlo: ogni accesso e refresh viene rifiutato (ACCOUNT_DISABLED), le sue sessioni terminano e ogni token che possiede viene rifiutato; false lo riattiva |
DELETE |
/api/admin/users/:uid |
Elimina un singolo utente. Le sue sessioni terminano, e ogni access token che possiede viene rifiutato da quella richiesta in avanti |
POST |
/api/admin/users/:uid/reset-password |
Reimposta la password di un utente senza richiedere quella attuale. rebase auth reset-password la richiama, e scrive direttamente nel database solo quando il backend non è raggiungibile; in entrambi i casi le sessioni dell’account terminano |
DELETE |
/api/admin/users/:uid/mfa |
Rimuove i secondi fattori e i codici di recupero di un utente, e termina le sue sessioni — per chi ha perso entrambi |
GET |
/api/admin/roles |
admin e i ruoli dichiarati dalla collezione users, con i loro scope |
POST |
/api/admin/bootstrap |
Consente al primo utente registrato di rivendicare il ruolo di amministratore quando non ne esiste alcuno. Rifiutato in produzione — vedi Bootstrap del primo utente |
Tutti gli endpoint delle API dati richiedono un header Authorization: Bearer <token> valido quando requireAuth: true (impostazione predefinita).
Policy delle password
Sezione intitolata “Policy delle password”Una nuova password richiede almeno 8 caratteri, con una lettera maiuscola, una
minuscola e una cifra. Registrazione, reimpostazione e modifica rifiutano una
password che non le rispetta con 400 WEAK_PASSWORD, elencando ogni regola
infranta, e lo stesso fa un amministratore che sceglie una password:
POST /api/admin/users, una creazione sulla collection degli utenti tramite la
data API (POST /api/data/users, il suo /bulk, una scrittura via socket o
MCP), PUT /api/admin/users/:uid e
POST /api/admin/users/:uid/reset-password. Su un /bulk, il messaggio inizia
con Row <n>: (contando da 0), come gli altri errori di riga del bulk.
Per creare account le cui password sono state impostate da un altro sistema,
come in un’importazione, invia skipPasswordPolicy: true nel corpo della
creazione (su ogni riga di un /bulk). Il server lo rispetta solo per un
amministratore o per la chiave di servizio; per chiunque altro non rinuncia a
nulla, e il rifiuto lo dice. Non viene mai memorizzato. L’importazione del
pannello di amministrazione lo invia per ogni riga che contiene una password.
L’accesso non verifica mai la policy, quindi una password più vecchia continua a funzionare. Per impostare
regole tue, passa auth.hooks.validatePasswordStrength da un backend
ejected. Il primo admin creato da
REBASE_ADMIN_PASSWORD è soggetto a una regola diversa: almeno 12 caratteri, e
nient’altro.
Formato della risposta
Sezione intitolata “Formato della risposta”Ogni endpoint che emette una sessione risponde con la stessa struttura (envelope) — register,
login, ciascun provider OAuth, magic-link/verify, otp/verify, anonymous,
anonymous/link e mfa/challenge/verify:
{ "user": { "uid": "8f1c2a6e-…", "email": "jane@example.com", "displayName": "Jane Doe", "photoURL": null, "providerId": "password", "isAnonymous": false, "emailVerified": true, "roles": ["editor"], "metadata": {} }, "tokens": { "accessToken": "eyJhbGciOi…", "refreshToken": "9b2e…", "accessTokenExpiresAt": 1700000000000 }}Invia l’access token nell’header Authorization: Bearer <accessToken>.
accessTokenExpiresAt è espresso in millisecondi epoch.
POST /api/auth/refresh risponde con la stessa struttura, con l’eccezione che user
viene omesso completamente quando l’account non può essere riletto, quindi è da
considerarsi opzionale in questo caso.
providerId indica come è stato effettuato l’accesso della sessione: password,
anonymous, magic-link, otp, mfa (un accesso completato con un secondo
fattore), oppure l’id del provider, ad esempio google. Viene memorizzato insieme
alla sessione al momento dell’accesso, quindi refresh e GET /api/auth/me
restituiscono la stessa risposta per tutta la durata della sessione. Una sessione
il cui accesso è avvenuto prima della 0.24 legge password.
Cambiare un indirizzo email
Sezione intitolata “Cambiare un indirizzo email”Un utente autenticato sposta il proprio account su un altro indirizzo in due passaggi:
POST /api/auth/change-email { newEmail }registra la modifica e invia via email un link,<frontend>/confirm-email-change?token=…, al nuovo indirizzo, e un avviso senza link a quello attuale. Il link vive 24 ore, e una nuova richiesta sostituisce l’ultima.GET /api/auth/mesegnala l’indirizzo in attesa comependingEmail.POST /api/auth/confirm-email-change { token }sposta l’account: il nuovo indirizzo diventa il suo indirizzo, verificato. Ogni identità OAuth il cui provider garantiva per il vecchio indirizzo viene scollegata (removedProvidersle elenca), perché altrimenti chiunque controlli il vecchio indirizzo potrebbe ancora accedere tramite esso, e qualsiasi link di reset inviato al vecchio indirizzo smette di funzionare. Le sessioni vengono mantenute.
Il nuovo indirizzo non viene riservato mentre il link è in attesa: tenerlo
riservato permetterebbe a qualsiasi account di impedire a un estraneo di
registrarsi con il proprio indirizzo. Se un altro account ha già quell’indirizzo
nel momento in cui il link viene seguito, il link risponde 409 EMAIL_EXISTS e
nulla viene spostato; tra due account che richiedono lo stesso indirizzo, lo
ottiene il primo che segue il proprio link. L’hook beforeEmailChange può
rifiutare un indirizzo, come fa beforeUserCreate alla registrazione.
Nel CMS, l’indirizzo si cambia da Account settings → Profile, e il link apre
la schermata del CMS stesso, autenticato o no. Un altro frontend serve una
pagina su /confirm-email-change che richiama la route con il token del link.
Con cookieAuth abilitato, il refresh
token viene trasmesso come cookie httpOnly e tokens.refreshToken è una stringa vuota
nel body. L’access token non subisce variazioni.
Autenticazione a più fattori (TOTP)
Sezione intitolata “Autenticazione a più fattori (TOTP)”Un secondo fattore vincola l’accesso, non solo singole operazioni. Una volta che un
account ha un fattore TOTP verificato, nessuna route emette una sessione finché non
viene fornito un codice — l’accesso tramite password, ogni provider OAuth, magic link e
anonymous-link rifiutano tutti con 401 MFA_REQUIRED:
{ "error": { "code": "MFA_REQUIRED", "message": "Multi-factor authentication is required to complete sign-in.", "details": { "mfaToken": "<short-lived pre-auth token>", "factors": [{ "id": "…", "factorType": "totp", "friendlyName": "Phone" }] } }}mfaToken non è una sessione: ha uno scopo specifico, scade dopo cinque minuti
e viene rifiutato da qualsiasi route autenticata. Invialo come bearer token a
POST /api/auth/mfa/challenge (con un factorId) e successivamente a
POST /api/auth/mfa/challenge/verify (con il challengeId e il codice a sei cifre,
o un codice di recupero). Quest’ultima chiamata è quella che genera i token di accesso
e di aggiornamento, con livello aal2; il livello viene memorizzato nella sessione e
mantenuto durante le chiamate a POST /api/auth/refresh.
Anche la registrazione dei fattori è protetta. Il primo fattore di un account può essere
registrato da una sessione ordinaria, ma una volta verificato, enroll, verify e unenroll
richiedono tutti una sessione aal2 — altrimenti una password rubata permetterebbe di registrare
un fattore secondario proprio, effettuare l’escalation e cancellare quello legittimo.
La verifica è limitata su entrambi i fronti: una challenge scade dopo cinque tentativi errati, ogni account è limitato a dieci tentativi di verifica ogni 15 minuti (conteggiati per utente, quindi cambiare IP non serve a nulla) e un codice accettato viene registrato per il fattore in modo che non possa essere riutilizzato (replay attack) per il resto della sua finestra di tolleranza di ±1 step.
Nella tabella degli utenti, Reset two-step verification (DELETE /api/admin/users/:uid/mfa) e
Disable or enable account (PUT /api/admin/users/:uid { disabled }) sono
offerti a chiunque possieda users:write, così come lo sono le route;
l’interruttore non viene mai offerto sul proprio account, e un account che
supera il tuo viene rifiutato con il motivo fornito dal server.
Imposta MFA_ENCRYPTION_KEY (almeno 32 caratteri casuali) per crittografare i segreti TOTP
memorizzati. Senza di esso, il server ripiega su JWT_SECRET emettendo un avviso. Impostalo
prima che qualsiasi utente si registri: i segreti salvati non includono un identificativo
di chiave, quindi modificare la chiave in un secondo momento renderà indecifrabili i fattori
esistenti e i rispettivi proprietari non potranno completare la verifica.
Invitare i membri del team via email
Sezione intitolata “Invitare i membri del team via email”I flussi di invito richiedono la conversione di un indirizzo email in un user id, ma la collection
users è protetta da RLS per le richieste client. Invece di creare manualmente una funzione
server di amministrazione, puoi abilitare la funzione di ricerca integrata:
await initializeRebaseBackend({ auth: { // ... allowUserLookup: true, // enables POST /api/auth/find-user },});Poi, dal client:
const profile = await client.auth.findUserByEmail("teammate@example.com");// → { uid, displayName, photoURL } | null (never email/roles/metadata)if (profile) { await client.data.team_members.create({ team_id, user_id: profile.uid });}L’endpoint è riservato ai soli utenti autenticati e restituisce esclusivamente uid, displayName
e photoURL — mai l’email, i ruoli o i metadati dell’utente cercato. È disabilitato per
impostazione predefinita poiché consente a qualsiasi utente autenticato di verificare quali
indirizzi email possiedono un account; abilitalo solo se richiesto dalla UX dei tuoi inviti.
Contesto del database per Row-Level Security (RLS)
Sezione intitolata “Contesto del database per Row-Level Security (RLS)”Rebase collega direttamente l’autenticazione delle richieste alla Row-Level Security (RLS) di PostgreSQL. Ogni query di database eseguita tramite un driver con ambito utente (user-scoped) viene eseguita all’interno di una transazione di database (db.transaction()) che imposta parametri di configurazione locali alla transazione:
app.user_id— L’ID univoco dell’utente autenticato (uid). Il valore predefinito è'anon'per le richieste non autenticate.app.user_roles— Una stringa separata da virgole che elenca i ruoli assegnati all’utente.app.jwt— Una stringa JSON contenente l’intero payload dei claim JWT ({"sub": "<uid>", "roles": [...]}).
Questi parametri vengono configurati localmente per la durata della transazione utilizzando la funzione set_config di Postgres:
SELECT set_config('app.user_id', $1, true), set_config('app.user_roles', $2, true), set_config('app.jwt', $3, true);Funzioni helper per le policy PostgreSQL
Sezione intitolata “Funzioni helper per le policy PostgreSQL”Per semplificare la scrittura delle policy di Row-Level Security, Rebase crea delle funzioni helper nello schema auth durante il bootstrap del database:
rebase.uid()— Restituisce l’ID dell’utente autenticato cometext, oppureNULLse non impostato:CREATE OR REPLACE FUNCTION rebase.uid() RETURNS text AS $$SELECT NULLIF(current_setting('app.user_id', true), '');$$ LANGUAGE sql STABLE;rebase.roles()— Restituisce la stringa dei ruoli separati da virgole:CREATE OR REPLACE FUNCTION rebase.roles() RETURNS text AS $$SELECT COALESCE(NULLIF(current_setting('app.user_roles', true), ''), '');$$ LANGUAGE sql STABLE;rebase.jwt()— Restituisce l’intero payload JWT come oggettojsonb:CREATE OR REPLACE FUNCTION rebase.jwt() RETURNS jsonb AS $$SELECT COALESCE(NULLIF(current_setting('app.jwt', true), ''), '{}')::jsonb;$$ LANGUAGE sql STABLE;
Puoi utilizzare queste funzioni helper direttamente nelle tue regole di sicurezza personalizzate o nelle migrazioni del database:
CREATE POLICY owner_access ON posts FOR ALL TO public USING (author_id = rebase.uid() OR string_to_array(rebase.roles(), ',') && ARRAY['admin']);Token asimmetrici e JWKS
Sezione intitolata “Token asimmetrici e JWKS”Per impostazione predefinita, gli access token sono firmati con jwtSecret (HS256). Questo approccio
funziona, ma comporta che qualsiasi entità debba verificare un token debba possedere la chiave che lo
emette — quindi un gateway o un edge worker che controlla una sessione può anche contraffarla — e
la modifica del segreto disconnette tutti gli utenti contemporaneamente.
Configurando una chiave di firma, Rebase firmerà invece gli access token in modo asimmetrico,
pubblicando la chiave pubblica su /.well-known/jwks.json affinché chiunque possa verificarli:
auth: { jwtSecret: process.env.JWT_SECRET, signingKeys: [ { kid: "2026-08", privateKey: process.env.JWT_PRIVATE_KEY! } ]}Oppure dalle variabili d’ambiente, per una singola chiave:
JWT_PRIVATE_KEY="$(cat jwt-key.pem)"JWT_KEY_ID=2026-08Genera una chiave con:
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out jwt-key.pemAnche le chiavi RSA sono supportate e firmano con RS256; le chiavi EC P-256 firmano con ES256. Viene
configurata solo la chiave privata — la parte pubblica viene derivata da essa, impedendo disallineamenti
tra la coppia. jwtSecret rimane comunque obbligatorio: serve ancora a firmare i token con scopo specifico
(link di download, MFA in sospeso, reimpostazione della password) che solo questo server legge.
Rotazione di una chiave
Sezione intitolata “Rotazione di una chiave”Inserisci la nuova chiave per prima e mantieni elencata quella vecchia. I nuovi token verranno firmati dalla nuova chiave; i token già in circolazione continueranno a essere verificati con quella precedente fino alla loro scadenza, evitando la disconnessione degli utenti.
signingKeys: [ { kid: "2026-09", privateKey: process.env.JWT_PRIVATE_KEY_NEW! }, { kid: "2026-08", privateKey: process.env.JWT_PRIVATE_KEY_OLD! }]Una volta trascorso il tempo di vita dell’access token più longevo, rimuovi la voce precedente. Usa
activeKid se desideri pubblicare una chiave prima di iniziare a usarla per firmare.
Verifica altrove
Sezione intitolata “Verifica altrove”I token contengono il kid della chiave di firma nel loro header: è così che un verificatore
seleziona la chiave corretta dal JWKS e sa quando riscaricarlo dopo una rotazione. Qualsiasi
libreria standard esegue questa operazione automaticamente — ad esempio con jose:
import { createRemoteJWKSet, jwtVerify } from "jose";
const jwks = createRemoteJWKSet(new URL("https://api.example.com/.well-known/jwks.json"));const { payload } = await jwtVerify(token, jwks);Autenticazione con Service Key
Sezione intitolata “Autenticazione con Service Key”Per la comunicazione server-to-server (ad es. cron job, servizi esterni), configura una chiave di servizio (service key) statica:
auth: { serviceKey: process.env.REBASE_SERVICE_KEY, // ...}I client si autenticano con l’header Authorization: Bearer <service-key>.
Chiave interna per ogni avvio
Sezione intitolata “Chiave interna per ogni avvio”Se REBASE_SERVICE_KEY non viene fornita nella configurazione, Rebase genera automaticamente una chiave interna per ogni avvio casuale.
Questa chiave non viene mai registrata nei log e non lascia mai il processo. Viene utilizzata dal singleton rebase per autenticarsi rispetto alle API di control-plane del server stesso (auth, storage, ecc.). Ciò garantisce che i task amministrativi (come l’invio di un’email di benvenuto o la generazione di un URL di archiviazione) funzionino sempre fin da subito, sia in fase di sviluppo che in produzione, senza richiedere la gestione manuale delle chiavi.
Protezione da timing attack e requisiti delle chiavi
Sezione intitolata “Protezione da timing attack e requisiti delle chiavi”Per prevenire timing attack (attacchi basati sui tempi di esecuzione), Rebase convalida sia la chiave di servizio configurata dall’utente sia la chiave interna utilizzando un confronto tra stringhe a tempo costante (safeCompare). La chiave di servizio configurata dall’utente deve essere lunga almeno 32 caratteri; se viene configurata una chiave più corta di 32 caratteri, Rebase genererà un errore di configurazione all’avvio e adotterà un comportamento fail-closed (blocco di sicurezza).
Passaggi successivi
Sezione intitolata “Passaggi successivi”- Autenticazione — la configurazione da cui derivano queste route
- Adapter di autenticazione personalizzati — come sostituire il provider sottostante
- Regole di sicurezza (RLS) — cosa fa una policy con
rebase.uid() - Autenticazione con l’SDK tipizzato — come chiamare queste route dall’SDK