Come si condividono auth e pagamenti tra Next.js, Expo e Tauri?
Dipende dalla capacità di mantenere un solo modello di identità e un solo archivio di entitlement. Il web usa sessioni con cookie HttpOnly; Expo salva un bearer token in expo-secure-store dopo il callback OAuth via deep link; Tauri riusa la sessione web dentro la sua webview — tutte validate dalla stessa istanza Better Auth. I pagamenti seguono la stessa regola: 8 gateway web e RevenueCat IAP mobile confluiscono in un ledger di webhook idempotente che scrive entitlement a livello di workspace letti da ogni superficie.
Le due cuciture dove il SaaS multipiattaforma si strappa
Chiedi a un team che ha saldato un'app mobile su un SaaS web dove fa davvero male — la risposta non è mai la UI. Sono identità e denaro: il flusso OAuth che ritorna pulito nel browser deve tornare nell'app via deep link, e il cliente che ha pagato sul web si aspetta il suo entitlement sul telefono all'istante.
I team di solito lo scoprono dopo aver rilasciato due mezze soluzioni: logica di cookie che fallisce in silenzio dentro WKWebView e un entitlement RevenueCat che litiga con l'abbonamento Stripe nel database web. La riparazione è architetturale — ed è due volte la stessa: un modello di identità, un archivio di entitlement.
Un'istanza Better Auth, tre trasporti di sessione
supastack mantiene un'unica configurazione Better Auth in @supastack/auth con punti d'ingresso per superficie. Le due app web (Next.js 16 e TanStack Start) usano sessioni con cookie HttpOnly SameSite=Lax — sicure contro CSRF di default e amichevoli con i Server Components. L'app Expo scambia il completamento dell'OAuth o del magic link alla fine del callback deep link con un bearer token, lo persiste in expo-secure-store (iOS Keychain / Android Keystore) e lo invia come header Authorization, validato dallo stesso middleware di auth.
La shell desktop di Tauri è una webview singola che punta all'app web, quindi eredita direttamente la sessione web — niente terzo flusso di auth da mantenere. La guida auth mobile e la guida ai deep link documentano il contratto di callback; la nostra guida al monorepo web + mobile mostra il codice client condiviso.
// packages/auth/src/client.ts — one client, two storages
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client";
export function createCrossPlatformAuthClient(baseURL: string, storage?: any) {
return createAuthClient({
baseURL,
plugins: storage ? [expoClient({ scheme: "myapp", storage })] : [],
});
}
// Web: cookie session. Mobile: bearer token in SecureStore.
// Desktop: the web app's own session inside the Tauri webview.Otto gateway di pagamento, un contratto
Ogni provider di pagamento web — Stripe, Lemon Squeezy, Polar, Creem, Dodo, Paddle, Whop e PayPal — si innesta nello stesso contratto @supastack/billing, e ogni webhook firmato atterra prima in un ledger webhook_event idempotente prima di poter toccare gli entitlement. I retry dei provider e le consegne fuori ordine diventano no-op innocui al confine del database, non ticket di supporto.
Gli acquisti in-app mobile entrano nello stesso ledger via RevenueCat: le ricevute degli store sono verificate lato server e gli entitlement scritti sul workspace, non sul dispositivo — chi paga su iOS è subito pagato anche su web e desktop. La guida RevenueCat copre il cablaggio App Store / Play; i webhook di billing idempotenti spiegano perché il ledger è il pezzo portante.
Il modello di entitlement che rende noiosa la sincronizzazione
Perché resta noioso: gli entitlement vivono sul workspace e vengono risolti lato server. Nessun client trattiene lo stato «pagato» — il web chiede alla sessione, il mobile all'API, il desktop all'app web — e tutti e tre ricevono la stessa risposta perché c'è un solo record che può rispondere.
Ripristino acquisti su un telefono nuovo, rinnovo Stripe sul web, una postazione Agency aggiunta nella dashboard — ciascuna è una scrittura nelle stesse righe di entitlement che ogni superficie legge alla prossima richiesta. Non c'è motore di sync da costruire perché non c'è nulla da sincronizzare.
| Superficie | Trasporto sessione | Percorso di pagamento | Entitlement letti da |
|---|---|---|---|
| Next.js 16 web | Cookie HttpOnly | Uno qualsiasi degli 8 gateway → ledger webhook | Righe entitlement Postgres condivise |
| TanStack Start web | Cookie HttpOnly | Stessi adapter, stesso ledger | Righe entitlement Postgres condivise |
| Expo iOS / Android | Bearer token in expo-secure-store | RevenueCat IAP → ricevuta verificata server | Stesse righe (via API tipizzata) |
| Tauri 2 desktop | Sessione web nella webview | Checkout web dentro la shell | Stesse righe (via app web) |
| Estensione MV3 | Chiave API workspace | — (legge dati workspace) | Stesse righe (via /api/v1) |
Cosa non devi più costruire
Tradotto in serate di lavoro, questa è la differenza tra fondamenta e lista di feature: il ritorno di auth via deep link, la persistenza del token in SecureStore, otto integrazioni webhook a prova di replay, la verifica delle ricevute App Store e un modello di entitlement che non si contraddice mai tra dispositivi. Tutto consegnato, testato (693+ test senza credenziali) e documentato.
Il Full Bundle da $299 contiene tutte le superfici in un repo — postazioni e livelli agency nella pagina dei prezzi.
FAQ: condividere auth e pagamenti tra piattaforme
Le domande che i team fanno prima di impegnarsi sullo stack unificato:
- Gli utenti mobile fanno login separato dal web?, No. Il flusso OAuth o magic link torna nell'app via deep link ed emette la stessa sessione; chi emette è la stessa istanza Better Auth, quindi web e mobile rispondono allo stesso modo su «chi sei».
- E chi paga su iOS ma usa l'app web?, RevenueCat consegna l'acquisto lato server; l'entitlement è scritto nelle stesse righe di workspace che il web legge, e l'app web mostra «pagato» alla richiesta successiva.
- Si può partire solo col web e aggiungere le app dopo?, Sì — auth e billing vivono nei package condivisi, quindi le app mobile e desktop li ereditano nel momento in cui attivi quelle superfici.
- L'app Tauri ha bisogno di un checkout proprio?, No — la shell desktop riusa sessione e checkout dell'app web dentro la sua webview; non c'è una seconda superficie di pagamento da difendere.