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.Protectedin Expo Router instead ofuseEffectredirects. 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
},
}
);
What to look for:
-
persistSession: trueand a real storage adapter. -
detectSessionInUrl: falseon native. Generated code copied from a web example often leaves thistrue. - Only
EXPO_PUBLIC_variables in the client. If you see a service-role key in anEXPO_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-storeworks, 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 />;
}
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>
);
}
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
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);
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();
});
}
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
schemeinapp.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 });
- That URL is added to the allowed redirect URLs in your auth provider's dashboard.
- A
reset-passwordroute 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);
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
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)