Skip to main content
This guide has two parts: A. one-time Supabase + Apple config; B. the 3-step app wiring.
Architecture recap: AuthProviding is the seam (in AcornCore). The default AnonymousAuthProvider (guest, no backend) ships in core; SupabaseAuthProvider ships in the optional AcornCoreSupabase module. The flow gate (AppFlowCoordinator
  • AcornGateView) and the AuthScreen UI are backend-agnostic.

Part A — Backend setup (one-time, ~30 min)

A1. Create a Supabase project

  1. supabase.com → New project. Note the Project URL (https://<ref>.supabase.co) and the anon public key (Project Settings → API).
  2. These are the only two values the app needs. The anon key is safe to ship in the client; never ship the service-role key.

A2. Enable email auth

Authentication → Providers → Email is on by default. Decide whether to require email confirmation (Authentication → Settings). If on, signUp returns no session until the user confirms — AuthScreen already handles this (shows “check your email”, flips to sign-in).

A3. Enable Sign in with Apple

Apple requires Sign in with Apple if you offer any social login (Guideline 4.8), and it’s the primary iOS method. In Xcode: Target → Signing & Capabilities → + Capability → Sign in with Apple. In the Apple Developer portal:
  1. Identifiers → your App ID → enable “Sign in with Apple”.
  2. Create a Services ID (for the Supabase callback), enable Sign in with Apple, and set the return URL to https://<ref>.supabase.co/auth/v1/callback.
  3. Create a Sign in with Apple Key (.p8), note the Key ID and your Team ID.
In Supabase: Authentication → Providers → Apple → enter the Services ID (client id), Team ID, Key ID, and the .p8 contents. Save.
For native iOS the app uses ASAuthorizationAppleIDCredential directly (no web redirect), but Supabase still needs the Apple provider configured to verify the identity token server-side.

A4. Deploy the delete-user Edge Function (account deletion)

Apple requires in-app account deletion (Guideline 5.1.1(v)). Supabase has no client-side user delete, so SupabaseAuthProvider.deleteAccount() invokes an Edge Function that runs with the service-role key.
supabase/functions/delete-user/index.ts
import { createClient } from "https://esm.sh/@supabase/supabase-js@2"

Deno.serve(async (req) => {
  const authHeader = req.headers.get("Authorization")
  if (!authHeader) return new Response("Unauthorized", { status: 401 })

  // Admin client (service-role) — never exposed to the app.
  const admin = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  )

  // Identify the caller from their JWT, then delete that user.
  const jwt = authHeader.replace("Bearer ", "")
  const { data: { user }, error } = await admin.auth.getUser(jwt)
  if (error || !user) return new Response("Unauthorized", { status: 401 })

  // (Optional) delete the user's rows/storage here BEFORE deleting the user.
  const { error: delErr } = await admin.auth.admin.deleteUser(user.id)
  if (delErr) return new Response(delErr.message, { status: 500 })

  return new Response(JSON.stringify({ ok: true }), {
    headers: { "Content-Type": "application/json" },
  })
})
Deploy: supabase functions deploy delete-user. (SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY are injected automatically for deployed functions.) The provider’s deleteUserFunction defaults to "delete-user"; override in the initializer if you name it differently.

Part B — App wiring (3 steps)

1

Add the dependency

The app already depends on AcornCore. Add the AcornCoreSupabase product to the app target (it pulls supabase-swift). Local-package apps just add the product; SPM apps add https://github.com/supabase/supabase-swift.git transitively via the module.
2

Create the provider

// In your composition root / AppConfig
let auth: any AuthProviding = SupabaseAuthProvider(
    supabaseURL: URL(string: "https://<ref>.supabase.co")!,
    supabaseKey: AppConfig.supabaseAnonKey   // anon public key
    // deleteUserFunction: "delete-user"     // override only if renamed
)
Want guest mode instead (no backend)? Use AnonymousAuthProvider() — same protocol, nothing else changes. Want RevenueCat entitlements tied to the account? Pass the user id as appUserID to RevenueCatSubscriptionProvider after sign-in.
3

Gate the app and drop in the screen

let coordinator = AppFlowCoordinator(
    subscriptions: subscriptions,
    auth: auth,
    requiresAuth: true,          // gate on a real (non-anonymous) account
    authBeforePaywall: true      // onboarding → auth → paywall → main
)

AcornGateView(coordinator: coordinator) {
    OnboardingFlow { coordinator.completeOnboarding() }
} paywall: {
    PaywallScreen { await coordinator.handlePurchaseCompleted() }
} main: {
    RootTabView()
} auth: {
    AuthScreen(auth: auth, legalLinks: AppConfig.legalLinks) {
        coordinator.handleAuthChanged()        // re-resolve the phase on sign-in
    }
}
That’s it. The coordinator shows .auth whenever requiresAuth is set and no real account is present; AuthScreen calls handleAuthChanged() on success, which advances the flow.

Wiring account deletion to data reset (Settings)

deleteAccount() removes the auth user; pair it with DataResetting so the user’s local + remote app data goes too:
let resetter = CompositeDataResetter([
    LocalDataResetter(suiteNames: [AppConfig.appGroup], additionalWipe: { /* caches, SwiftData */ }),
    // + a remote resetter if you store user rows outside the delete-user function
])

// In your "Delete account" button:
try await resetter.deleteAllData()   // wipe app data
try await auth.deleteAccount()       // delete the auth user + signOut
coordinator.handleAuthChanged()      // back to .auth / .onboarding
(Or have the delete-user Edge Function delete the user’s rows server-side — then resetter only needs the local wipe.)

Pre-ship checklist

  • Supabase URL + anon key in config (not the service-role key).
  • Email confirmation decision made (on/off).
  • Sign in with Apple: Xcode capability + App ID + Services ID + key + Supabase provider.
  • delete-user Edge Function deployed; account-deletion control wired in Settings.
  • Live test in simulator/device: sign up, sign in (email + Apple), reset password, sign out, delete account; confirm the gate advances onboarding → auth → paywall → main.
  • Privacy: list “email”/“user id” in App Privacy; ensure Terms/Privacy links shown on AuthScreen (pass legalLinks).
What swift test can’t cover — verify these on a real simulator/device:
  • The Apple button requires a real bundle id + the capability + a device/simulator signed into an Apple ID.
  • The Supabase round-trip needs network + the configured project.
  • RevenueCatSubscriptionProvider(appUserID:) should be set to auth.currentUser?.id after sign-in so entitlements follow the account.