Mobile — Expo Development

Expo + Supabase Auth Stack for MVPs

Direct answer

Expo plus Supabase is my default MVP auth stack: Supabase provides Postgres, row-level security, and email, magic-link, and OAuth auth out of the box, and its JS client persists sessions in AsyncStorage on device. A working authenticated app is realistically a one-to-two-day setup. The real engineering effort lands in two places founders underestimate: row-level security policies, which are your actual security layer, and deep-link handling for OAuth and email confirmation flows.

Auth is the classic MVP time sink — it's undifferentiated work that still has to be right. The Expo and Supabase pairing compresses it to days without boxing you in later. Here's how I wire it on real projects, including the parts the quickstarts skip.

Key facts, with sources

  • 71% of State of React Native 2024 respondents reported using Expo's EAS Build, ahead of manual builds with Xcode (59.7%) and Android Studio (54.5%). (InfoQ)
  • Expo SDK 52, released November 12, 2024, enabled the New Architecture by default for all new projects, and Expo Go for SDK 52 and higher supports only the New Architecture. (Expo Changelog)
  • The official React Native documentation now recommends starting new apps with a framework, naming Expo, rather than initializing a bare project. (React Native documentation)
  • EAS build caching can speed up subsequent Android and iOS builds by up to 30% and is available to all users at no additional cost as of the SDK 57 cycle. (Expo Changelog)
  • Doctolib runs Expo tooling without EAS to scale developer experience on a healthcare app serving 90 million users, showing Expo's open-source tools work independently of its paid cloud services. (Doctolib Engineering (Medium))

Why this pairing wins for MVPs

Supabase collapses three procurement decisions into one: database (Postgres, not a proprietary store you'll migrate off later), auth (email/password, magic links, OAuth providers, phone), and authorization (row-level security enforced in the database itself). For an MVP that means one vendor, one client library, and a security model that doesn't depend on your API layer being perfect.

On the Expo side, the fit is clean because the Supabase JS client is pure JavaScript — no native module, no config plugin, no build implications. It runs identically in Expo Go, development builds, and production. The one mobile-specific requirement is telling the client to persist sessions in AsyncStorage instead of browser localStorage, which is a constructor option.

Client setup that behaves on mobile

The setup below is my standard baseline. The pieces that matter: AsyncStorage as the session store so users stay logged in across app restarts, autoRefreshToken so sessions renew silently in the background, and detectSessionInUrl disabled because that's a web-browser behavior that doesn't apply to native apps. The URL polyfill import handles a runtime gap in React Native that the Supabase client depends on.

Environment variables with the EXPO_PUBLIC_ prefix are embedded into the app bundle at build time — appropriate for the Supabase URL and anon key, which are public by design. Everything sensitive stays behind row-level security, never in the client.

lib/supabase.ts — mobile-correct client
import 'react-native-url-polyfill/auto';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { createClient } from '@supabase/supabase-js';

export const supabase = createClient(
  process.env.EXPO_PUBLIC_SUPABASE_URL!,
  process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
  {
    auth: {
      storage: AsyncStorage,
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: false,
    },
  }
);

Session state as the app's routing spine

I treat the Supabase session as the root state that drives navigation: on launch, read the persisted session, then subscribe to auth state changes so sign-ins, sign-outs, and token refreshes all flow through one listener. With expo-router this typically lives in the root layout, gating the authenticated route group. Resist scattering session checks across screens — one subscription, one source of truth.

The subtle detail is the loading state: there's a moment on cold start before the persisted session has been read, and if you route during it you'll flash the login screen at logged-in users. Hold navigation until the initial getSession resolves.

Session subscription at the root
import { useEffect, useState } from 'react';
import { Session } from '@supabase/supabase-js';
import { supabase } from '../lib/supabase';

export function useSession() {
  const [session, setSession] = useState<Session | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    supabase.auth.getSession().then(({ data }) => {
      setSession(data.session);
      setLoading(false);
    });

    const { data: sub } = supabase.auth.onAuthStateChange(
      (_event, s) => setSession(s)
    );
    return () => sub.subscription.unsubscribe();
  }, []);

  return { session, loading };
}

Deep links: where every team loses a day

Magic links and OAuth both end with a redirect, and on mobile that redirect must land back inside your app — which means a custom URL scheme in app config and redirect URLs registered in the Supabase dashboard that use it. This is the part of the stack where I see every team burn time: the scheme mismatches between environments, or works in a development build but was never tested in the production binary, or the redirect allow-list in the dashboard is missing the production scheme.

My advice: pick the app scheme early, register every environment's redirect explicitly, and test the full round trip — tap link in email, land in app, session established — on a physical device for both a development and a production-profile build before calling auth done. For OAuth flows, an in-app auth session browser gives a much better experience than bouncing users to the system browser.

Row-level security is the actual security model

The anon key ships inside your app binary; anyone can extract it and call your database's API directly. That's by design — which means row-level security policies are not an optional hardening step, they are the entire authorization model. Enable RLS on every table from day one, write policies that scope reads and writes to the authenticated user, and test them with a second account before launch. In audits of Supabase-backed MVPs, missing or over-permissive RLS is the most serious finding I encounter, and it's depressingly common.

As for outgrowing the stack: the exit paths are honest. Supabase is Postgres, so the data layer moves anywhere Postgres runs, and auth can be fronted or replaced incrementally. For most MVPs the stack scales well past product-market fit — the point of choosing it is that you get to defer that conversation without accumulating a trap.

When to hire senior help

Senior help pays off when deciding between managed and prebuild workflows, setting up EAS-based CI/CD and OTA update channels, or untangling a project that was ejected prematurely. An experienced Expo engineer can usually configure build, submit, and update pipelines in days, a task that costs first-time teams weeks of trial and error. If your stack includes React Native + Python + AI, a senior engineer who owns the full product beats coordinating multiple juniors.

Bottom line

Dhairya Senjaliya ships Mobile — Expo Development projects worldwide — book a scoping call to discuss your specific situation.

Common pitfalls to avoid

  • Ejecting to the bare workflow at the first native requirement instead of using config plugins and development builds, permanently giving up managed upgrades
  • Testing only in Expo Go and misconfiguring runtime versions for expo-updates, so an OTA update ships JavaScript that crashes against mismatched native binaries
  • Burning EAS build minutes on every commit in CI without caching or local builds, turning a convenience service into a surprise line item
  • Skipping several SDK upgrades in a row, then being forced into a large breaking migration when app store target API level deadlines arrive

Frequently asked questions

Is Supabase auth secure enough for a production mobile app?

Yes, provided you use it as designed: the anon key in your app is public by design, and row-level security policies in Postgres enforce who can read and write what. The dangerous misconfiguration is skipping RLS — then anyone with your anon key can query your data directly. Enable RLS on every table, write per-user policies, and verify them with a second test account before launch.

Does Supabase work in Expo Go or do I need a development build?

The Supabase JS client is pure JavaScript, so email/password auth and database access work in Expo Go with no native build. You'll want a development build once you're testing custom URL scheme deep links for OAuth and magic-link flows realistically, since production-style redirect handling depends on your app's scheme rather than Expo Go's.

Why do Supabase magic links not open my Expo app?

Almost always a redirect configuration gap: the email's redirect URL must use your app's custom scheme, that scheme must be set in your Expo app config, and the exact URL must be listed in Supabase's redirect allow-list. Test the full round trip on a physical device for each environment — schemes that work in a development build are frequently missing from production configuration.

Should we use Expo or plain React Native for a new app?

The React Native docs themselves now recommend starting with a framework, and Expo is the primary one; 71% of surveyed developers already build with EAS. Expo today supports custom native code through development builds and config plugins, so the old limitations that forced teams to avoid it mostly no longer apply.

Does Expo lock us into their platform?

No; expo prebuild can generate standard native iOS and Android projects at any time, and the open-source tooling works without Expo's paid EAS services, as demonstrated by Doctolib running Expo without EAS on an app with 90 million users. EAS Build, Submit, and Updates are optional conveniences, not requirements.

Is Expo production-ready for a serious commercial app?

Yes; Expo Go is only a development sandbox, while production apps ship as normal store binaries built with EAS or locally. Plan for the SDK release cadence of roughly three versions per year, since staying current is required to keep up with store policy and React Native releases.

Bottom line: Dhairya Senjaliya ships Mobile — Expo Development projects worldwide. Book a scoping call at https://dhairyasenjaliya.com/#book-call.

Sources

Related guides

Keep up with new guides

New deep-dive guides on React Native, Python, and AI ship regularly. Subscribe via RSS or follow on LinkedIn.

Want help implementing this?

30-minute scoping call · Clear milestones · Senior engineer ownership