Authentication
Wire email, Apple, Google, session storage, and account management.
Authentication is Integrated through Supabase Auth. The app supports email/password, email OTP sign-in, sign-up verification, forgot/reset password, email/password changes, account deletion, and native Apple + Google sign-in.
Runtime shape
AuthProvider in lib/auth.tsx is the app's auth source of truth. It loads the
initial Supabase session, subscribes to onAuthStateChange, exposes useAuth(),
and centralizes auth methods so screens do not call Supabase Auth directly.
AuthGate is guest-first. It does not wall the whole app behind login; public
screens can render signed out, and protected surfaces show SignInGate. Once a
signed-in user lands in the auth stack, AuthGate dismisses the login flow back
to the requesting screen or home.
Session storage
lib/supabase.ts creates createClient<Database>(...) with platform-aware
persistence:
| Platform | Storage | Why |
|---|---|---|
| iOS / Android | ChunkedSecureStore | Supabase sessions can exceed SecureStore's practical per-value limit, so lib/secure-store-chunked.ts splits long values into 1800-character chunks |
| Web | window.localStorage | Web builds use browser storage and disable URL session detection |
Keep auth tokens in the Supabase client storage layer. Do not mirror sessions into AsyncStorage, app preferences, or analytics properties.
Email flows
Screens live in app/(auth)/, while form UI lives in components/auth/.
Validation uses react-hook-form with Zod schemas from lib/auth-schemas.ts.
| Flow | Code path | Supabase call |
|---|---|---|
| Sign in with password | SignInForm → signIn | signInWithPassword |
| Sign in with code | SignInForm → sendLoginOtp / verifyLoginOtp | signInWithOtp({ shouldCreateUser: false }) → verifyOtp(type: "email") |
| Sign up | SignUpForm → signUp | signUp with full_name user metadata |
| Verify sign-up | SignUpForm → verifyEmailOtp | verifyOtp(type: "email") |
| Forgot password | forgot-password.tsx → resetPassword | resetPasswordForEmail |
| Complete reset | reset-password.tsx → completePasswordReset | verifyOtp(type: "recovery") → updateUser({ password }) |
| Change email | account-security.tsx → changeEmail / verifyEmailChangeOtp | updateUser({ email }) → verifyOtp(type: "email_change") |
| Change password | account-security.tsx → changePassword | updateUser({ password }) |
Credential submissions and OTP verification do not retry because retries can
consume one-time tokens or trip provider rate limits. Idempotent helper calls
use the small retryAsync wrapper in lib/network.ts.
Apple Sign-In
Apple is native iOS only. components/auth/social-connections.tsx lazily
requires expo-apple-authentication when Platform.OS === "ios", requests full
name and email scopes, then passes the returned identity token to:
supabase.auth.signInWithIdToken({
provider: "apple",
token: credential.identityToken,
nonce: credential.nonce,
});When Apple returns name fields on first sign-in, the app updates auth metadata
and upserts the profiles.full_name value. On Android and web, the module is
not loaded and the button reports that Apple Sign-In is unavailable.
Setup checklist:
- Enable Sign in with Apple for the Apple app identifier.
- Enable the Apple provider in Supabase Auth.
- Rebuild the native app after capability or config-plugin changes.
Google Sign-In
Google is native iOS/Android only. The app lazily requires
@react-native-google-signin/google-signin, configures it with
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID and optional
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID, asks Android for Play Services when needed,
then sends the Google ID token to Supabase with signInWithIdToken.
Create OAuth clients
Create a Google Cloud Web OAuth client and add this redirect URI:
https://<ref>.supabase.co/auth/v1/callbackCreate iOS OAuth clients for every bundle identifier you build:
expo.soarstarter.com.dev
expo.soarstarter.com.preview
expo.soarstarter.comCreate Android OAuth clients for every package name and signing certificate SHA-1 you test or ship.
Configure Supabase
In Authentication → Providers → Google, enable Google and set:
Client IDs = <web-client-id>,<ios-client-id-1>,<ios-client-id-2>,...
Client Secret = <web-client-secret>
Skip nonce checks = ONPut the Web client ID first and include each native client ID whose token audience Supabase should accept.
Set Expo env values
EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID=<web-client-id>.apps.googleusercontent.com
EXPO_PUBLIC_GOOGLE_IOS_CLIENT_ID=<ios-client-id>.apps.googleusercontent.comRestart Metro after env changes. Rebuild a development client after changing native identifiers or config-plugin behavior.
If Google is unavailable in the current runtime, the button shows a friendly toast instead of crashing. GitHub appears in the shared social button map, but it currently shows a placeholder "not yet available" message.
Account & Security
app/account-security.tsx provides signed-in account operations:
- Change email and verify the new email code.
- Change password.
- Sign out other sessions.
- Enable or disable biometric lock when native biometrics are available.
- Delete the account through the
delete-accountEdge Function.
Delete account calls supabase.functions.invoke("delete-account") with the
signed-in JWT. The function deletes user-owned rows, removes files under
sample-uploads/<user-id>/, then deletes the auth user. Rows and files go first
so a mid-flight failure leaves the user signed in and the call retryable.
subscriptions and payments cascade from auth.users.
Apple requires in-app account deletion for App Store approval. Keep this flow available if users can create accounts in the iOS app.
Signed-out feature gates
Use SignInGate from components/sign-in-gate.tsx for protected feature
surfaces inside the otherwise-public app. It renders a localized sign-in call to
action and opens /login, letting the auth stack dismiss itself after the
session lands.
Verify authentication
- Sign up with email/password, verify the email code, and confirm the auth
user plus
profilesrow exist. - Sign out, then sign in with password and with an email code.
- Run forgot/reset password from the auth stack.
- In a native build, test Apple on iOS and Google on iOS/Android after provider setup.
- Delete a test account and confirm its auth user, rows, and upload files are removed.
