# Auth — overview

Authentication in Fayz is abstracted behind a contract: the `AuthAdapter`. The same `useAuth` works whether you're running against Supabase in production or against a mock adapter in previews. The auth backend is a detail you swap — the code that consumes auth doesn't change.

## The contract: `AuthAdapter`

An adapter implements the session operations — `getSession`, `signIn`, `signUp`, `signOut`, `signInWithOAuth`, `resetPassword`, `onAuthStateChange` and friends. `@fayz-ai/auth` ships two out of the box:

- **`createSupabaseAuthAdapter({ supabaseUrl, supabaseAnonKey })`** — real auth via Supabase Auth. Maps the Supabase user and session to the Fayz types (`AuthUser`, `AuthSession`).
- **`createMockAuthAdapter()`** — a simulated user persisted in `localStorage`, for development and previews without a backend. It comes already "signed in" as a demo user.

Switching from one to the other is switching the factory — nothing else.

## Consuming it: `AuthProvider` + `useAuth`

In a code-first app, you mount `AuthProvider` at the root, passing the adapter you chose, and read the state with the `useAuth` hook:

```tsx
import { AuthProvider, useAuth, createSupabaseAuthAdapter } from '@fayz-ai/auth'

const adapter = createSupabaseAuthAdapter({
  supabaseUrl: import.meta.env.VITE_SUPABASE_URL,
  supabaseAnonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
})

function Root() {
  return (
    <AuthProvider adapter={adapter}>
      <App />
    </AuthProvider>
  )
}
```

```tsx
function UserBadge() {
  const { user, isAuthenticated, signOut } = useAuth()
  if (!isAuthenticated) return <span>Guest</span>
  return <button onClick={signOut}>{user?.fullName}</button>
}
```

`useAuth` exposes `user`, `session`, `isLoading`, `isAuthenticated`, `error` and the actions (`signIn`, `signUp`, `signOut`, `signInWithOAuth`, `resetPassword`, …). Calling it outside an `<AuthProvider>` throws — on purpose.

## How this works in the manifest-first scaffold

Here's the honest part. In the app generated by `fayz create` — the manifest-first path — **you don't mount `AuthProvider` by hand**. The platform runtime reads `backend.provider` from the manifest and wires the matching adapter for you: `mock` uses the simulated adapter; `supabase` uses the real one with the credentials from your `.env.local`. Auth "follows" the backend you declare — switching `provider` in the manifest also switches the auth adapter.

The explicit `AuthProvider` / `useAuth` you saw above are the **code-first** path: apps that mount their own React tree (via `defineSaas` or headless) and want direct control over where the session enters. Both paths use the same `AuthAdapter` contract — only who mounts the provider changes.

{% callout type="info" %}
In short: **manifest-first** → the auth adapter follows `backend.provider`, no code. **Code-first / headless** → you mount `AuthProvider` with the adapter of your choice. Identical contract either way.
{% /callout %}

## Auth and multi-tenancy

The session resolved by auth is what feeds the `public.user_tenant_ids()` function in the database — meaning auth is what ultimately decides which rows RLS lets you see. Auth and isolation go together; see [RLS and multi-tenancy](/en/docs/data/rls).

---

Next: build your first capability in [Incubator](/en/docs/building-plugins/incubator).
