Skip to main content

What It Does

User authentication with email/password, persistent session management via secure storage, password recovery through web interim callback chain, and email change with double-confirmation flow.

User Flow (Mobile)

1

Unauthenticated State

App opens → if not authenticated, only Discover, Search, and Profile tabs are visible (Home and Saved are hidden).
2

Login

Taps login → app/login.tsx → enters email + password → client-side Zod validation (loginSchema) → supabase.auth.signInWithPassword() directly.
3

Session Established

On success → AuthProvider stores session, app re-renders with all 5 tabs visible.

Screens & Components

PlatformScreenPurpose
Mobileapp/login.tsxLogin form, “Forgot password?” link
Mobileapp/forgot-password.tsxPassword reset request form
Web Interim/auth/confirmServer-side OTP verification (entry point for all email links)
Web Interim/reset-passwordPassword reset page (after clicking recovery link)
Web Interim/confirmation-successEmail confirmed success page
Web Interim/email-change-pendingWaiting for new email confirmation
Web Interim/change-emailEmail change success page
Web Interim/auth/auth-errorAuth link invalid/expired error page

API Route (Phase 2 — Web Client Only)

POST /api/auth/login
  • Validates body with loginSchema
  • Calls supabase.auth.signInWithPassword({ email, password })
  • Returns session data (access_token, refresh_token)
The mobile app calls supabase.auth.signInWithPassword() directly via the AuthProvider — it does NOT go through this web API route. This route exists for the web client (Phase 2).

Mobile Auth Architecture

AuthProvider

AuthProvider (src/providers/AuthProvider.tsx) wraps the entire app and exposes:
Property/MethodPurpose
userCurrent Supabase user object
sessionCurrent session with tokens
profileCurrent user’s profile from profiles table
isAuthenticatedBoolean
isLoadingAuth state loading
signIn(email, password)Email/password login
signUp(data: RegisterFormValues)Registration (also enrolls in Loops.so)
signOut()Logout + cache clear
refreshProfile()Re-fetch profile from database
refreshSession()Force session refresh (e.g., after email change)
deleteAccount()Calls delete_user_account RPC + signs out
resetPassword(email)Sends password reset email via Supabase
handleUnauthorized()Called by useApiErrorHandler on 401 → auto-logout

Supabase Client Configuration

apps/mobile/src/lib/supabase.ts differs from the web client:
  • Uses SecureStoreAdapter wrapping expo-secure-store for token persistence
  • detectSessionInUrl: false (mobile doesn’t handle URL-based auth)
  • flowType: 'pkce' (Proof Key for Code Exchange)
  • Env vars: EXPO_PUBLIC_SUPABASE_URL, EXPO_PUBLIC_SUPABASE_ANON_KEY

Provider Hierarchy

SafeAreaProvider
  └─ ThemeProvider
       └─ GestureHandlerRootView
            └─ QueryClientProvider (staleTime: 5min, retry: 2)
                 └─ AuthProvider
                      └─ ToastProvider
                           └─ ProfileProvider
                                └─ SearchProvider
                                     └─ AuthOverlayProvider
                                          └─ SaveProvider
                                               └─ Stack Navigator

Tab Visibility (Auth-Dependent)

StateVisible Tabs
AuthenticatedHome, Discover, Search, Saved, Profile (5 tabs)
UnauthenticatedDiscover, Search, Profile (3 tabs — Home and Saved hidden via href: null)

AuthOverlayProvider

Intercepts actions that require auth (favorite, save, comment, follow) and shows a login prompt overlay instead of navigating to login screen. Smoother UX for unauthenticated browsing.

Auth Callback Chain (Web Interim)

The web interim (apps/web-interim) handles all Supabase email-based auth flows. This is critical infrastructure.

Dual Auth Flow (Server-side OTP vs Client-side Token Exchange)

FlowRouteWhen Used
Server-side OTP/auth/confirm (route handler)Primary path — Supabase email links with token_hash + type params
Client-side PKCEAuthCallbackHandler (component)Fallback — code query param exchanged for session
Client-side ImplicitAuthCallbackHandler (component)Fallback — # hash fragment with access_token, refresh_token, type

Callback Processing

There are two paths for auth callbacks, depending on how Supabase formats the email link: Path 1: Server-side OTP verification (/auth/confirm) The primary path. Supabase email links include token_hash and type query params. The /auth/confirm route handler calls supabase.auth.verifyOtp() server-side, then redirects:
OTP TypeRedirect
signup / email/confirmation-success
recovery/reset-password
email_change/change-email
Error/auth/auth-error
Path 2: Client-side token exchange (AuthCallbackHandler) Fallback for PKCE flows or hash-fragment tokens. The AuthCallbackHandler component (mounted on destination pages) detects:
  • code query param → PKCE code exchange → session established
  • # hash fragment with type=recovery → redirects to /reset-password
  • # hash fragment with type=signup → redirects to /confirmation-success
  • Error params → redirects to /auth/auth-error

Password Recovery Flow

  1. User taps “Forgot password?” on mobile → app/forgot-password.tsx
  2. Enters email → supabase.auth.resetPasswordForEmail(email, { redirectTo }) — Supabase sends recovery email directly (no web API route)
  3. User clicks link → web interim detects type=recovery/reset-password
  4. Sets new password via supabase.auth.updateUser({ password })
The recovery flow is web-only — the user is sent to the web interim’s reset password page, not a mobile screen.

Email Change Flow (Double Confirmation)

  1. User initiates email change from mobile settings
  2. Supabase sends confirmation to OLD email → web interim → /email-change-pending
  3. Supabase sends confirmation to NEW email → web interim → /change-email

API Error Handling

apps/mobile/src/lib/api.ts provides:
  • ApiError class with code, status, details + convenience getters (isUnauthorized, isNotFound, isServerError)
  • handleSupabaseQuery(query) — wraps Supabase queries with error handling
  • getAuthToken() — retrieves current session token for API calls
  • useApiErrorHandler hook integrates with AuthProvider.handleUnauthorized() to auto-logout on 401
Both packages/shared/src/api/client.ts and apps/mobile/src/lib/api.ts define their own ApiError class. The mobile version is more feature-rich. Be careful to import the correct one.