DEV Community

Cover image for AI built your React Native app. Now check the auth: 6 things to verify before you ship
Famitha M A for RapidNative

Posted on Originally published at rapidnative.com

AI built your React Native app. Now check the auth: 6 things to verify before you ship

TL;DR

  • AI app builders now produce real React Native + Expo projects, but auth is the part most likely to look done and still break in production.
  • Six checks catch most of it: session storage, route-level gating, hydration, token refresh, deep-link callbacks, and Row Level Security.
  • Use Stack.Protected in Expo Router instead of useEffect redirects. It removes the "flash of private screen" bug.
  • If you're choosing a tool to build native iOS and Android apps with AI, the first question is whether it outputs a native project (React Native, Swift, Kotlin) or a web app. That one answer rules out half the list.
  • Comparison table and "who each tool is for" are near the end.

Why auth is where AI-built apps break

Ask any AI app builder for "a habit tracker with login" and you'll get a sign-in screen in a minute. It renders, it calls the backend, it navigates to Home. Demo done.

Then a real user kills the app, opens it two days later, and lands on the login screen again. Or they tap a password-reset email and the link opens a browser tab that goes nowhere. Or someone discovers they can read every other user's rows with the key that's sitting in your JS bundle.

None of that shows up in a preview. All of it shows up in reviews. So here's the checklist, with the code you want to see in the generated project.

The examples use Expo Router and Supabase because that's the most common stack AI builders output for mobile today. The ideas carry over to Clerk, Firebase or your own API.


1. Where does the session live?

Open the file that creates the auth client. If there's no storage option, the session lives in memory and disappears on every cold start.

// lib/supabase.ts
import { AppState, Platform } from 'react-native';
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_PUBLISHABLE_KEY!,
  {
    auth: {
      ...(Platform.OS !== 'web' ? { storage: AsyncStorage } : {}),
      autoRefreshToken: true,
      persistSession: true,
      detectSessionInUrl: false, // no URL bar on native
    },
  }
);
Enter fullscreen mode Exit fullscreen mode

What to look for:

  • persistSession: true and a real storage adapter.
  • detectSessionInUrl: false on native. Generated code copied from a web example often leaves this true.
  • Only EXPO_PUBLIC_ variables in the client. If you see a service-role key in an EXPO_PUBLIC_ var, stop and rotate it. Anything with that prefix is shipped inside the app.

Want tokens in the Keychain/Keystore instead of AsyncStorage? expo-secure-store works, but it has a value size limit, so large sessions need an encrypted-AsyncStorage wrapper. Check what your generator did before assuming "secure".


2. Is auth gated at the router, or per screen?

This is the most common pattern in generated code:

// app/(app)/home.tsx  ❌ per-screen redirect
export default function Home() {
  const { session } = useSession();
  useEffect(() => {
    if (!session) router.replace('/login');
  }, [session]);
  return <Dashboard />;
}
Enter fullscreen mode Exit fullscreen mode

It works, kind of. The private screen renders for a frame before the redirect, every new screen needs the same effect, and deep links can land a logged-out user on a private route.

Expo Router has a declarative answer: Stack.Protected. Screens inside it only exist while guard is true.

// app/_layout.tsx  ✅ route-level gating
import { Stack } from 'expo-router';
import { SessionProvider, useSession } from '@/lib/session';

export default function RootLayout() {
  return (
    <SessionProvider>
      <RootNavigator />
    </SessionProvider>
  );
}

function RootNavigator() {
  const { session } = useSession();

  return (
    <Stack screenOptions={{ headerShown: false }}>
      <Stack.Protected guard={!!session}>
        <Stack.Screen name="(app)" />
      </Stack.Protected>

      <Stack.Protected guard={!session}>
        <Stack.Screen name="login" />
        <Stack.Screen name="signup" />
      </Stack.Protected>
    </Stack>
  );
}
Enter fullscreen mode Exit fullscreen mode

File structure that goes with it:

app/
  _layout.tsx        # the guard lives here, once
  login.tsx
  signup.tsx
  (app)/
    _layout.tsx      # tabs for signed-in users
    home.tsx
    settings.tsx
Enter fullscreen mode Exit fullscreen mode

When session flips, Expo Router moves the user to the first available screen. No redirects sprinkled across the app. Docs: Protected routes.


3. What renders while the session is loading?

Reading a session from storage is async. If the guard runs before that finishes, session is null and a signed-in user sees the login screen for a moment, or gets bounced there.

Keep the splash screen up until you know:

// lib/session.tsx
import * as SplashScreen from 'expo-splash-screen';
import { createContext, useContext, useEffect, useState } from 'react';
import type { Session } from '@supabase/supabase-js';
import { supabase } from './supabase';

SplashScreen.preventAutoHideAsync();

const SessionContext = createContext<{ session: Session | null }>({ session: null });

export function SessionProvider({ children }: { children: React.ReactNode }) {
  const [session, setSession] = useState<Session | null>(null);
  const [ready, setReady] = useState(false);

  useEffect(() => {
    supabase.auth.getSession().then(({ data }) => {
      setSession(data.session);
      setReady(true);
      SplashScreen.hideAsync();
    });

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

  if (!ready) return null; // splash stays visible

  return <SessionContext.Provider value={{ session }}>{children}</SessionContext.Provider>;
}

export const useSession = () => useContext(SessionContext);
Enter fullscreen mode Exit fullscreen mode

Check two things: an onAuthStateChange subscription exists (so sign-out anywhere updates the guard), and it's cleaned up.


4. Does the token refresh when the app comes back?

JavaScript timers pause when a mobile app is backgrounded. Without this, a user who returns after an hour makes requests with an expired access token:

// lib/supabase.ts (continued)
if (Platform.OS !== 'web') {
  AppState.addEventListener('change', (state) => {
    if (state === 'active') supabase.auth.startAutoRefresh();
    else supabase.auth.stopAutoRefresh();
  });
}
Enter fullscreen mode Exit fullscreen mode

It's four lines and it's missing from a surprising amount of generated code, because web examples don't need it.


5. Do email links come back into the app?

Magic links, sign-up confirmation and password reset all send the user out to their mail app. Something has to bring them back.

  • A scheme in app.json / app.config.ts, e.g. "scheme": "habits".
  • The redirect URL passed to auth calls is built from that scheme, not hard-coded to localhost:
import * as Linking from 'expo-linking';

const redirectTo = Linking.createURL('/reset-password');

await supabase.auth.resetPasswordForEmail(email, { redirectTo });
Enter fullscreen mode Exit fullscreen mode
  • That URL is added to the allowed redirect URLs in your auth provider's dashboard.
  • A reset-password route exists and reads the token from the incoming URL.

Test it on a real device with a real inbox. The simulator hides most of the ways this fails.


6. Is the database actually locked down?

The publishable key is in your bundle. Anyone can extract it. That's fine only if every table has Row Level Security on and sensible policies.

alter table public.habits enable row level security;

create policy "own habits: read"
  on public.habits for select
  using (auth.uid() = user_id);

create policy "own habits: write"
  on public.habits for insert
  with check (auth.uid() = user_id);
Enter fullscreen mode Exit fullscreen mode

Quick test: sign in as user A, copy a row id that belongs to user B, and try to fetch it from the app. You should get nothing back. If the generator created tables without RLS, that's a ship blocker, not a nice-to-have.


The checklist, in one block

[ ] Session persisted with a storage adapter; detectSessionInUrl false on native
[ ] No secret keys in EXPO_PUBLIC_ vars
[ ] Auth gated once, in the root layout, with Stack.Protected
[ ] Splash held until session is read; onAuthStateChange subscribed
[ ] AppState listener starts/stops token refresh
[ ] App scheme + redirect URLs set; reset-password route exists
[ ] RLS enabled on every table, tested with two accounts
Enter fullscreen mode Exit fullscreen mode

Paste that into your PR template or your prompt. AI builders get noticeably better when you ask for these by name.


What is the best AI app builder for native iOS and Android apps?

Short answer: the one that outputs a native project you can open, export and ship, not a web app inside a wrapper. After that, it comes down to how much of the backend (auth, database, storage) is included and how you want to publish.

Here's a fair comparison using criteria that matter for the checklist above. It's based on each vendor's own site as of October 2026. Things a site doesn't state are marked as such rather than guessed.

Tool What it outputs iOS + Android Auth / backend included Code export Preview on device
RapidNative React Native + Expo (Expo Router) Yes, one codebase (plus web) Postgres, auth, storage, realtime per project; or bring your own Supabase Yes (paid plans) QR code on iPhone and Android
Rork Native mobile apps from chat; Rork Max targets Swift for Apple devices Yes Rork Backend: data, auth, storage Not stated on homepage Not stated on homepage
Lovable Web apps Not as native projects Yes, via Supabase Yes Browser
AI coding agent + template (Claude Code, Cursor) Whatever you scaffold Yes, if you start from Expo Whatever you wire up You own the repo Expo Go / dev build

How to read it:

  • Lovable is excellent at what it's built for, which is web apps. If you need a store-listed iOS and Android app, you'll be converting or wrapping later.
  • Rork alternatives usually come up when people want a specific stack or ownership of the code. If you want a React Native/Expo codebase you can open in your editor, check that before you commit.
  • Coding agents give the most control and the least scaffolding. You'll be doing all six checks above by hand, which is fine if you're a developer and slow if you're not.

Where RapidNative fits

RapidNative is an AI app builder that turns a prompt, sketch, screenshot or product spec into a React Native and Expo app for iOS, Android and web, with auth and a Postgres database included in every project.

For this article's checklist, the relevant parts are that its projects use Expo Router for navigation, ship with sign-up, sign-in and password reset out of the box, and let you open the code in your own IDE or export it. That means you can run the six checks above on the actual project, rather than trusting a preview. If you want to try that loop, RapidNative has a free tier that doesn't need a card.

Who each option is for

  • Non-technical founders who want a mobile app: pick a builder that includes the backend and lets you test on your own phone by QR. Then hand this checklist to whichever developer reviews it before launch.
  • Developers prototyping for clients: a builder that exports a clean Expo project saves the boring first week, and you keep full control after.
  • Teams with an existing web app on Lovable: decide early whether you need a real native app. Rebuilding later costs more than choosing right now.
  • Developers who want total control: a coding agent plus a good Expo template, and this checklist taped to your monitor.

FAQ

Can AI build native iOS and Android apps?
Yes. Several tools now generate React Native/Expo or Swift/Kotlin projects rather than web apps. The output is real native code, but you should still review auth, permissions and store compliance before submitting.

Is React Native "native"?
React Native renders real platform UI components, not a web view. It's the same approach used by many large production apps, and Expo adds build, update and store tooling on top.

What's the most common auth bug in AI-generated mobile apps?
Missing session persistence or missing token refresh on app foreground. Both pass every demo and fail on the second day of real use.


That's the list I'd want run on any AI-built mobile app before it hits the store. What did your AI builder get wrong on auth? Drop the weirdest bug you've found in the comments, and which tool generated it.

Top comments (0)