Hoe deel je auth en betalingen tussen Next.js, Expo en Tauri?
Het hangt af van of je één identiteitsmodel en één entitlementopslag kunt behouden. Web gebruikt HttpOnly-cookiesessies; Expo slaat na de deep-link OAuth-callback een bearer token op in expo-secure-store; Tauri hergebruikt de websessie in zijn webview — allemaal gevalideerd door dezelfde Better Auth-instantie. Betalingen volgen dezelfde regel: 8 webgateways en RevenueCat mobile IAP komen samen in één idempotent webhook-ledger dat workspace-brede entitlements schrijft die elke surface leest.
De twee naden waar cross-platform SaaS scheurt
Vraag een team dat een mobiele app aan een web-SaaS heeft gelast waar het echt pijn doet — het antwoord is nooit UI. Het is identiteit en geld: de OAuth-flow die in de browser netjes terugspringt, moet via een deep link terug in de app; en de klant die op het web heeft betaald, verwacht zijn entitlement meteen in zijn telefoon.
Teams ontdekken dit meestal nadat ze twee halve oplossingen hebben geleverd: cookielogica die in WKWebView stilletjes faalt, en een RevenueCat-entitlement die vecht met het Stripe-abonnement in de webdatabase. De reparatie is architecturaal — en het is twee keer dezelfde: één identiteitsmodel, één entitlementopslag.
Eén Better Auth-instantie, drie sessietransporten
supastack houdt één Better Auth-configuratie in @supastack/auth met ingangspunten per surface. Beide web-apps (Next.js 16 en TanStack Start) gebruiken HttpOnly SameSite=Lax-cookiesessies — standaard CSRF-veilig en vriendelijk voor Server Components. De Expo-app ruilt aan het einde van de deep-link-callback de OAuth- of magic-link-voltooiing in voor een bearer token, persist dat in expo-secure-store (iOS Keychain / Android Keystore) en stuurt het als Authorization-header, gevalideerd door dezelfde auth-middleware.
Tauri's desktopshell is een enkele webview naar de web-app en erft dus direct de websessie — geen derde auth-flow om te onderhouden. De mobiele auth-gids en deep-linkgids documenteren het callbackcontract; onze web + mobile monorepo-gids toont de gedeelde clientcode.
// 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.Acht betalingsgateways, één contract
Elke webbetalingsprovider — Stripe, Lemon Squeezy, Polar, Creem, Dodo, Paddle, Whop en PayPal — plugt in hetzelfde @supastack/billing-contract, en elke ondertekende webhook landt eerst in een idempotent webhook_event-ledger voordat hij entitlements mag aanraken. Provider-retries en out-of-order levering worden aan de databasegrens onschuldige no-ops in plaats van supporttickets.
Mobiele in-app-aankopen voeden hetzelfde ledger via RevenueCat: storereceipts worden server-side geverifieerd en entitlements worden op de workspace geschreven, niet op het apparaat — wie op iOS betaalt, is meteen ook betaald op web en desktop. De RevenueCat-gids behandelt de App Store/Play-bedrading; idempotente billingwebhooks legt uit waarom het ledger het dragende deel is.
Het entitlementmodel dat synchronisatie saai maakt
Waarom het saai blijft: entitlements leven op de workspace en worden server-side opgelost. Geen enkele client houdt 'betaald' vast — het web vraagt het aan de sessie, mobile aan de API, desktop aan de web-app — en alle drie krijgen hetzelfde antwoord omdat er maar één record is om te antwoorden.
Aankoop herstellen op een nieuwe telefoon, een Stripe-verlenging op het web, een Agency-seat toegevoegd in het dashboard — elk is een write in dezelfde entitlementregels die elke surface bij de volgende vraag leest. Er is geen sync-engine om te bouwen omdat er niets is om te synchroniseren.
| Platform | Sessietransport | Betalingspad | Entitlements gelezen uit |
|---|---|---|---|
| Next.js 16 web | HttpOnly-cookie | Elk van de 8 gateways → webhook-ledger | Gedeelde Postgres-entitlementregels |
| TanStack Start web | HttpOnly-cookie | Dezelfde adapters, hetzelfde ledger | Gedeelde Postgres-entitlementregels |
| Expo iOS / Android | Bearer token in expo-secure-store | RevenueCat IAP → servergeverifieerde receipt | Dezelfde regels (via getypte API) |
| Tauri 2 desktop | Websessie in de webview | Webcheckout binnen de shell | Dezelfde regels (via web-app) |
| MV3-extensie | Workspace-API-key | — (leest workspacedata) | Dezelfde regels (via /api/v1) |
Wat je niet meer hoeft te bouwen
Omgerekend naar avonden werk is dat het verschil tussen fundament en featurelijst: de deep-link auth-return, SecureStore-tokenpersistentie, acht replay-veilige webhook-integraties, App Store-receiptverificatie en een entitlementmodel dat zich tussen apparaten nooit tegenspreekt. Allemaal geleverd, getest (693+ tests zonder credentials) en gedocumenteerd.
De $299 Full Bundle bevat alle surfaces in één repo — seataantallen en agencyniveaus op de prijspagina.
FAQ: auth & betalingen delen over platforms
Vragen die teams stellen voordat ze op de uniforme stack committeren:
- Loggen mobiele gebruikers apart van het web in?, Nee. De OAuth- of magic-linkflow keert via deep link terug in de app en geeft dezelfde sessie uit; dezelfde Better Auth-instantie geeft uit, dus web en mobile antwoorden hetzelfde op 'wie ben je'.
- Wat als iemand op iOS betaalt maar de web-app gebruikt?, RevenueCat levert de aankoop server-side; het entitlement wordt in dezelfde workspaceregels geschreven die het web leest, en de web-app toont betaald bij het volgende verzoek.
- Kun je alleen met web beginnen en later apps toevoegen?, Ja — auth en billing leven in de gedeelde packages, dus de mobile- en desktop-apps erven ze zodra je die surfaces aanzet.
- Heeft de Tauri-app een eigen checkout nodig?, Nee — de desktopshell hergebruikt sessie en checkout van de web-app in zijn webview; er is geen tweede betalingssurface om te verdedigen.