Most teams building a web + mobile product end up with two auth integrations that slowly drift apart. You don't need that. Here's how to run a single Supabase auth layer across a Next.js web app and an Expo mobile app in a monorepo β including the gotchas nobody warns you about.
1. Keep the Supabase client framework-agnostic
Depend only on @supabase/supabase-js. Expose a factory that takes the storage adapter as an argument:
export function createSupabaseClient({ url, anonKey, storage, detectSessionInUrl }) {
return createClient(url, anonKey, {
auth: { storage, autoRefreshToken: true, persistSession: true, detectSessionInUrl },
});
}
Web uses default browser storage; mobile passes AsyncStorage. Same client, same auth helpers, same session context everywhere.
2. Reference env vars literally
Next and Expo only inline literal process.env.NEXT_PUBLIC_X / EXPO_PUBLIC_X accesses. A dynamic process.env[key] is undefined in the bundle. Pass the literals into the factory from each app.
3. Authenticate API routes with a Bearer token, not cookies
Cookie sessions are awkward to share with a mobile app. Instead, send the access token from the client session and validate it server-side:
const token = req.headers.get('Authorization')?.replace('Bearer ', '');
const { data } = await admin.auth.getUser(token);
Identical from web fetch and from the mobile app.
4. The monorepo gotchas
- One React version across the workspace (Expo pins it) β mixing breaks shared components.
-
node-linker=hoistedso pnpm's symlinks don't trip Metro. - A
metro.config.jsthat adds the workspace root towatchFoldersandnodeModulesPaths.
5. Keep billing server-authoritative
Let clients read their subscription (RLS, select-own) but never write it. The Stripe webhook (service role) is the only writer. No trust placed in the client.
Close
That's the whole pattern: a portable client, token-based route auth, and a monorepo that respects Metro's quirks. I packaged it (plus Stripe, push, RLS, docs) into a starter kit called Shipstack if you'd rather not wire it yourself β you can grab it here. But the patterns above are yours to use either way.
Top comments (0)