There's a specific moment in every mobile project where you realize you've spent a week on auth and shipped nothing else. Auth is a tar pit — it looks simple in the docs and gets weird the moment real users start signing in on real devices.
Here's an honest comparison of the three flows we actually use, in order of "how quickly can I ship this."
Magic links (Supabase): fastest to wire, slowest to use
Wire time: 30 minutes.
const { error } = await supabase.auth.signInWithOtp({
email,
options: { emailRedirectTo: "yourapp://auth-callback" },
});Email arrives, user taps link, deep link opens your app, session lands. No OAuth configuration. No entitlements. No bundle identifier headaches. Ships in an afternoon.
The problem: your users hate it. Switching to their email app, waiting for the mail to arrive (sometimes 10+ seconds on slow inboxes), tapping the link, getting bounced back to your app — it's 3-4 context switches for what should be one tap.
Use it: for dev, for passwordless backup, for accounts that sign in once a month. Don't use it: as your primary mobile auth flow if anyone on iOS is going to use your app daily.
Apple Sign-In: fastest to use, fiddliest to wire
Wire time: half a day.
Apple Sign-In is the best mobile auth UX that exists. Face ID confirms, your app gets the user in under a second. Apple mandates it if you have any other social auth, which is the one thing making everyone ship it.
The wiring is where it gets irritating:
- You need an App ID in the Apple Developer portal with Sign In with Apple capability enabled.
- You need an Apple Services ID configured for the web redirect (even if you're only doing iOS).
- The first-time signup returns the user's name; subsequent signins don't. Store it or lose it.
- The
emailfield can be a proxy (abc123@privaterelay.appleid.com); don't assume it's the user's real address.
See our Apple Sign-In guide for the exact click-paths.
import * as AppleAuthentication from "expo-apple-authentication";
const credential = await AppleAuthentication.signInAsync({
requestedScopes: [
AppleAuthentication.AppleAuthenticationScope.FULL_NAME,
AppleAuthentication.AppleAuthenticationScope.EMAIL,
],
});
// Send credential.identityToken to Supabase:
await supabase.auth.signInWithIdToken({
provider: "apple",
token: credential.identityToken!,
});Use it: as your primary iOS login flow. Always. Don't use it: as your only option — some users want email, let them have email.
Google Sign-In: fastest between web+mobile parity, SHA-1 will haunt you
Wire time: a full day, possibly more if you haven't done it before.
Google Sign-In requires three different SHA-1 certificate fingerprints registered in Google Cloud Console:
- Your debug keystore's SHA-1 (for local development).
- Your upload keystore's SHA-1 (for the build you submit to Play).
- Your Play App Signing SHA-1 (what Play signs the final APK with).
All three must be in the OAuth client. If you skip any, you'll get DEVELOPER_ERROR: 10 at runtime and spend two hours thinking it's your code.
The single command that saves you:
# Ours prints all three, but if you're not using our kit, use keytool:
keytool -list -v -keystore ~/.android/debug.keystore \
-alias androiddebugkey -storepass android -keypass androidUse it: when you need cross-platform identity with existing web users, or on Android as your primary flow.
The practical recommendation
For a new app:
- Wire up magic links first (30 min, dev-time unblocks).
- Add Apple Sign-In for iOS (half day, primary login).
- Add Google Sign-In for Android (full day, primary login).
- Keep magic links visible as a fallback "sign in another way."
You'll be done in two days. Or use a starter kit that has all four wired and just swap in your OAuth client IDs.
Read more
React Native + Supabase: The Stack We'd Pick to Ship an App in 2026
A opinionated look at why React Native with Expo and Supabase is the fastest way to a production mobile app in 2026 — and the three places it bites you.
Expo SDK Upgrades: The Gotchas Nobody Warns You About
A battle-tested field guide to Expo SDK upgrades — what breaks, what quietly changes behavior, and how to test an upgrade without shipping a broken build.
RevenueCat vs Stripe for Mobile In-App Purchases: When to Use Which
A decision framework for mobile payments: when RevenueCat's abstraction pays for itself, when Stripe's native sheet is enough, and when you need both.