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)
Unauthenticated State
App opens → if not authenticated, only Discover, Search, and Profile tabs are visible (Home and Saved are hidden).
Login
Taps login →
app/login.tsx → enters email + password → client-side Zod validation (loginSchema) → supabase.auth.signInWithPassword() directly.Screens & Components
| Platform | Screen | Purpose |
|---|---|---|
| Mobile | app/login.tsx | Login form, “Forgot password?” link |
| Mobile | app/forgot-password.tsx | Password reset request form |
| Web Interim | /auth/confirm | Server-side OTP verification (entry point for all email links) |
| Web Interim | /reset-password | Password reset page (after clicking recovery link) |
| Web Interim | /confirmation-success | Email confirmed success page |
| Web Interim | /email-change-pending | Waiting for new email confirmation |
| Web Interim | /change-email | Email change success page |
| Web Interim | /auth/auth-error | Auth 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/Method | Purpose |
|---|---|
user | Current Supabase user object |
session | Current session with tokens |
profile | Current user’s profile from profiles table |
isAuthenticated | Boolean |
isLoading | Auth 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
SecureStoreAdapterwrappingexpo-secure-storefor 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
Tab Visibility (Auth-Dependent)
| State | Visible Tabs |
|---|---|
| Authenticated | Home, Discover, Search, Saved, Profile (5 tabs) |
| Unauthenticated | Discover, 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)
| Flow | Route | When Used |
|---|---|---|
| Server-side OTP | /auth/confirm (route handler) | Primary path — Supabase email links with token_hash + type params |
| Client-side PKCE | AuthCallbackHandler (component) | Fallback — code query param exchanged for session |
| Client-side Implicit | AuthCallbackHandler (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 Type | Redirect |
|---|---|
signup / email | /confirmation-success |
recovery | /reset-password |
email_change | /change-email |
| Error | /auth/auth-error |
AuthCallbackHandler)
Fallback for PKCE flows or hash-fragment tokens. The AuthCallbackHandler component (mounted on destination pages) detects:
codequery param → PKCE code exchange → session established#hash fragment withtype=recovery→ redirects to/reset-password#hash fragment withtype=signup→ redirects to/confirmation-success- Error params → redirects to
/auth/auth-error
Password Recovery Flow
- User taps “Forgot password?” on mobile →
app/forgot-password.tsx - Enters email →
supabase.auth.resetPasswordForEmail(email, { redirectTo })— Supabase sends recovery email directly (no web API route) - User clicks link → web interim detects
type=recovery→/reset-password - 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)
- User initiates email change from mobile settings
- Supabase sends confirmation to OLD email → web interim →
/email-change-pending - Supabase sends confirmation to NEW email → web interim →
/change-email
API Error Handling
apps/mobile/src/lib/api.ts provides:
ApiErrorclass withcode,status,details+ convenience getters (isUnauthorized,isNotFound,isServerError)handleSupabaseQuery(query)— wraps Supabase queries with error handlinggetAuthToken()— retrieves current session token for API callsuseApiErrorHandlerhook integrates withAuthProvider.handleUnauthorized()to auto-logout on 401