> ## Documentation Index
> Fetch the complete documentation index at: https://docs.theacornkit.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Auth setup (Supabase)

> Stand up the auth backend and wire AcornKit's auth into your app.

This guide has two parts: **A.** one-time Supabase + Apple config; **B.** the 3-step app wiring.

<Note>
  **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.
</Note>

## 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.

<Note>
  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.
</Note>

### 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.

```ts supabase/functions/delete-user/index.ts theme={null}
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)

<Steps>
  <Step title="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.
  </Step>

  <Step title="Create the provider">
    ```swift theme={null}
    // 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.
  </Step>

  <Step title="Gate the app and drop in the screen">
    ```swift theme={null}
    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.
  </Step>
</Steps>

### 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:

```swift theme={null}
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`).

<Warning>
  **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.
</Warning>
