Como compartilhar auth e pagamentos entre Next.js, Expo e Tauri?
Depende de manter um único modelo de identidade e um único armazenamento de entitlements. A web usa sessões por cookie HttpOnly; o Expo guarda um bearer token no expo-secure-store após o callback OAuth por deep link; o Tauri reusa a sessão web dentro da sua webview — todas validadas pela mesma instância Better Auth. Os pagamentos seguem a mesma regra: 8 gateways web e RevenueCat IAP mobile convergem num ledger de webhooks idempotente que escreve entitlements em nível de workspace lidos por cada superfície.
As duas costuras onde o SaaS multi-plataforma rasga
Pergunte a qualquer equipe que soldou um app mobile num SaaS web onde dói de verdade — a resposta nunca é UI. É identidade e dinheiro: o fluxo OAuth que volta limpo no navegador precisa voltar para o app via deep link, e o cliente que pagou na web espera seu entitlement no telefone na hora.
As equipes geralmente descobrem depois de lançar duas meias-soluções: lógica de cookies que falha em silêncio dentro do WKWebView e um entitlement RevenueCat que briga com a assinatura Stripe no banco web. O conserto é arquitetural — e é o mesmo duas vezes: um modelo de identidade, um armazenamento de entitlements.
Uma instância Better Auth, três transportes de sessão
O supastack mantém uma única configuração Better Auth em @supastack/auth com pontos de entrada por superfície. Os dois apps web (Next.js 16 e TanStack Start) usam sessões de cookie HttpOnly SameSite=Lax — seguras contra CSRF por padrão e amigáveis a Server Components. O app Expo troca a conclusão do OAuth ou magic link no fim do callback deep link por um bearer token, o persiste no expo-secure-store (iOS Keychain / Android Keystore) e o envia como header Authorization, validado pelo mesmo middleware de auth.
O shell desktop do Tauri é uma webview única apontando para o app web, então herda a sessão web diretamente — sem um terceiro fluxo de auth para manter. O guia de auth mobile e o guia de deep links documentam o contrato de callback; nosso guia de monorepo web + mobile mostra o código cliente compartilhado.
// 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.Oito gateways de pagamento, um contrato
Todo provedor de pagamento web — Stripe, Lemon Squeezy, Polar, Creem, Dodo, Paddle, Whop e PayPal — se encaixa no mesmo contrato @supastack/billing, e cada webhook assinado cai primeiro num ledger webhook_event idempotente antes de poder tocar entitlements. Retries de provedores e entregas fora de ordem viram no-ops inofensivos na fronteira do banco, não tickets de suporte.
As compras in-app mobile entram no mesmo ledger via RevenueCat: os recibos das lojas são verificados no servidor e os entitlements escritos no workspace, não no dispositivo — quem paga no iOS já está pago na hora também na web e no desktop. O guia RevenueCat cobre a fiação App Store / Play; webhooks de billing idempotentes explica por que o ledger é a peça estrutural.
O modelo de entitlements que torna a sincronização entediante
Por que fica entediante: os entitlements vivem no workspace e são resolvidos no servidor. Nenhum cliente guarda o estado «pago» — a web pergunta à sessão, o mobile à API, o desktop ao app web — e os três recebem a mesma resposta porque só há um registro capaz de responder.
Restaurar compra num telefone novo, renovação Stripe na web, um assento Agency adicionado no dashboard — cada um é uma escrita nas mesmas linhas de entitlement que cada superfície lê na próxima consulta. Não há motor de sync para construir porque não há nada para sincronizar.
| Superfície | Transporte de sessão | Caminho de pagamento | Entitlements lidos de |
|---|---|---|---|
| Next.js 16 web | Cookie HttpOnly | Qualquer um dos 8 gateways → ledger de webhooks | Linhas de entitlement Postgres compartilhadas |
| TanStack Start web | Cookie HttpOnly | Mesmos adaptadores, mesmo ledger | Linhas de entitlement Postgres compartilhadas |
| Expo iOS / Android | Bearer token no expo-secure-store | RevenueCat IAP → recibo verificado no servidor | Mesmas linhas (via API tipada) |
| Tauri 2 desktop | Sessão web dentro da webview | Checkout web dentro do shell | Mesmas linhas (via app web) |
| Extensão MV3 | Chave de API workspace | — (lê dados do workspace) | Mesmas linhas (via /api/v1) |
O que você deixa de construir
Convertido em noites de trabalho, essa é a diferença entre fundação e lista de features: o retorno de auth por deep link, a persistência de token no SecureStore, oito integrações de webhook à prova de replay, a verificação de recibos da App Store e um modelo de entitlements que nunca se contradiz entre dispositivos. Tudo entregue, testado (693+ testes sem credenciais) e documentado.
O Full Bundle de $299 contém todas as superfícies num repo — números de assentos e níveis de agência na página de preços.
FAQ: compartilhar auth e pagamentos entre plataformas
Perguntas que equipes fazem antes de se comprometer com a stack unificada:
- Usuários mobile fazem login separado da web?, Não. O fluxo OAuth ou magic link retorna ao app por deep link e emite a mesma sessão; quem emite é a mesma instância Better Auth, então web e mobile respondem igual a «quem é você».
- E quem paga no iOS mas usa o app web?, A RevenueCat entrega a compra no lado servidor; o entitlement é escrito nas mesmas linhas de workspace que a web lê, e o app web mostra «pago» na próxima requisição.
- Dá para começar só com web e adicionar apps depois?, Sim — auth e billing vivem nos pacotes compartilhados, então os apps mobile e desktop os herdam quando você liga essas superfícies.
- O app Tauri precisa de checkout próprio?, Não — o shell desktop reusa sessão e checkout do app web dentro da sua webview; não há uma segunda superfície de pagamento para defender.