Supabase Auth in Native Apps: Sessions, Deep Links, and OAuth
OAuth in a native app has to leave your app and come back. Here's how our five Supabase-backed templates handle the deep-link round trip on every platform.
Web OAuth is easy: you redirect to Google, the user approves, Google redirects back to your URL, done. It works because the browser owns both the departure and the return.
Native apps have no such luxury. When your iOS, Android, or desktop app sends a user to Google, the user leaves your app entirely — into Safari, Chrome, or the system browser. Getting them back, with a session, is the whole problem. Five SoarStarter templates use Supabase Auth and solve this the same conceptual way with platform-specific plumbing: SwiftUI, Kotlin, Expo, Electron, and Tauri. Here's the shared model.
The round trip
Every native OAuth flow is the same three moves:
- Leave — open the system browser at Supabase's OAuth URL, passing a
redirectTothat points back at your app via a custom URL scheme (e.g.soar://auth-callback). - Return — the OS routes that custom-scheme URL back to your running app. This is the deep link, and it carries the auth code.
- Exchange — hand the code to Supabase (
exchangeCodeForSession), which returns a session you persist. PKCE ties the request and the response together so the code can't be intercepted and replayed.
The concept is identical everywhere. What differs is step 2 — how each operating system delivers that soar:// URL back to your app — and that's where native development earns its reputation.
The same idea, five sets of plumbing
iOS (SwiftUI). The app registers a custom scheme and resolves incoming URLs in the AppDelegate. A DeepLinkResolver parses both the soar:// custom-scheme URL and (planned) https:// Universal Links via an AASA file, so the same code path handles OAuth callbacks and any other deep link.
Android (Kotlin). An intent filter on the callback activity claims the scheme; Android hands the URL to the app, which passes it to the Supabase client to complete the exchange.
Expo (React Native). expo-linking provides the URL listener and generates the redirect URL; the session is then persisted in expo-secure-store rather than plain async storage, because a session token is a credential.
Electron. The desktop wrinkle is that the OS launches a second copy of your app when it sees the custom scheme. So the template registers the scheme with app.setAsDefaultProtocolClient, then catches the URL two different ways: macOS delivers it through the open-url event, while Windows and Linux deliver it as an argument to a second-instance launch, which is forwarded to the already-running window.
Tauri. The @tauri-apps/plugin-deep-link plugin exposes onOpenUrl, and a single-instance guard in the Rust core focuses the existing window and forwards the URL instead of spawning a duplicate — the same "second instance" concern Electron has, handled by the Tauri plugin.
Five platforms, one mental model: register a scheme, catch the return, exchange the code. If you've only done web auth, the surprising part is how much of the work is just "make the OS give my running app the URL."
Sessions have to survive a restart
Getting a session once isn't enough — users expect to stay logged in. Two rules the templates follow:
- Store the session in secure storage, not plain preferences. Keychain on iOS,
expo-secure-storeon Expo, the platform secure store on desktop. A refresh token is a credential; treat it like one. - Restore and refresh on launch. On cold start, the app rehydrates the persisted session and lets the Supabase client refresh it if the access token has expired, so a returning user lands signed in without re-authenticating.
Why Supabase for the native side
We use Better Auth on the web templates, so it's fair to ask why the native ones diverge. On mobile and desktop we want auth, a hosted Postgres, storage, and native client SDKs as one coherent package — and Supabase provides exactly that, with official SDKs for Swift, Kotlin, and JavaScript. The subscriptions table that backs our RevenueCat entitlement model lives in the same Supabase database, so auth and billing share one backend.
The deep-link dance is genuinely the fiddliest part of native auth, and it's the part that's already wired, tested, and identical in shape across all five templates. Start from the authentication docs for your platform to see the exact scheme registration and callback handling.