Skip to Content
Native IntegrationSupabase Auth

SupabaseAuth — identity + account pairing (Phase 2)

Status: Fundament built + on master (schema live, slim PWA client, migrations CI). PWA login gate + account pairing + extension sign-in + relay sync are planned (see Plans below). This doc is the native-client contract for the new auth layer.

1. Role — what Supabase is (and isn’t) in VSRelay

Supabase provides identity + metadata only. It does not carry agent traffic.

Client (PWA / native) Supabase Extension (Cursor/VS Code) ──────────────────── ───────── ────────────────────────── OAuth login ───────────► auth.users (GitHub/Google) read my rooms (RLS) ─────► rooms / devices (RLS) ◄───── registers its room │ pick a room (extension sign-in) ▼ relay_client_auth{roomId, deviceToken} ─────────────────────── Node relay (TRANSPORT, unchanged) ──────────────► (relay validates room/device against Supabase; stays a transparent forwarder)
  • Supabase = who you are (OAuth identity) + which editors/devices you own (rooms/devices).
  • Relay = the transport. Unchanged, still a transparent forwarder. It does not speak Supabase auth to the client; it validates room/device credentials that originate from Supabase (via a server-side sync — “Option A”).
  • Extension = registers its room under your account so your phone can find it.

The agent-control protocol (prompts, sessions, terminals, files, diagnostics, recovery) is unchanged — Supabase sits in front of it as the identity gate + the pairing directory.

2. Auth model

  • OAuth only — GitHub + Google. No email/password, no magic links, no anonymous sign-in.
  • PKCE flow for every client (web SPA + native).
  • Session = a Supabase JWT (access_token + refresh_token), auto-refreshed. auth.uid() (the user id) is the identity used by all Row-Level-Security policies.
  • OAuth runs in the system browser — never an embedded webview (App Store requirement + security). Web: a redirect. Native: ASWebAuthenticationSession (iOS) / Custom Tab (Android) → a deep link.
  • Session storage: web = Supabase-managed localStorage; native = OS keychain (Keychain / Keystore), never plain prefs.

3. Schema + RLS (the tables a client touches)

All in public, all RLS owner-scoped (TO authenticated, (select auth.uid()) = user_id; UPDATE has USING + WITH CHECK). Tokens are stored as hashes, never raw.

TablePurposeClient reads
profilesmirror of auth.users (github_username, display_name, avatar_url); auto-created by a trigger on signupyour own profile (for the header chip)
roomsa registered editor instance — room_id (matches the relay’s room), relay_url, extension_token_hash, label, last_seen_atyour rooms = the pairing list
devicesa paired phone — room_id, device_token_hash, platform, label, last_seen_atyour devices

A client only ever sees its own rows (RLS). The Data API is reached with the publishable key as apikey + the user’s access_token as the bearer (so RLS scopes to that user).

4. How a client authenticates

Web (PWA — reference implementation):

  1. auth.signInWithOAuth({ provider: 'github' | 'google', options: { redirectTo: window.location.origin } }) → system browser.
  2. Provider → Supabase callback → back to the app origin with a ?code=.
  3. The client (with detectSessionInUrl) exchanges the code → session persisted; onAuthStateChange fires.
  4. The client points its DB reads at the user’s token (setDbAuth(access_token)) so RLS works.

Native (iOS/Android):

  1. signInWithOAuth({ provider, redirectTo: 'vsrelay://auth-callback' }) opened in ASWebAuthenticationSession.
  2. Provider → Supabase callback → deep link vsrelay://auth-callback?code=….
  3. App captures the deep link → exchangeCodeForSession(code) → session stored in the keychain by the SDK.

vsrelay://auth-callback, https://app.vsrelay.dev/**, https://vsrelay.dev/**, and http://localhost:5173/** are in Supabase’s redirect allow-list. (The PWA is served at https://app.vsrelay.dev; the vsrelay.dev entry is retained as a transition-period safety net.)

5. Account-based pairing (replaces manual token paste)

After login, the client reads its rooms (RLS-scoped — no server code needed):

GET {SUPABASE_URL}/rest/v1/rooms?select=* (apikey: publishable, Authorization: Bearer <access_token>) → [{ room_id, relay_url, label, last_seen_at, ... }]

The user taps a room → the client connects to the relay exactly as today:

wss://<relay_url> → { type: 'relay_client_auth', roomId, deviceToken, ... }

deviceToken is issued (once) when the device is paired and stored client-side (keychain on native). No copy/paste — the phone signs in and sees the user’s editors. (The extension populates rooms by signing in itself; the relay validates the roomId/deviceToken against Supabase via a server-side sync.)

6. Relationship to the relay (doctrine)

  • The relay stays a transparent forwarder — it never inspects payloads. Validating a roomId/deviceToken against Supabase is admission/routing, not payload inspection → compliant.
  • For v1 the relay’s room/device list is synced from Supabase (Option A — zero relay protocol change). A live Supabase query on the relay (Option B) is a later optimization.
  • E2E payload encryption is a separate future phase (Phase C) — independent of this identity layer.

7. Doctrine for native clients

  • Thin renderer. Supabase owns the auth flow; the client opens the system browser, stores the session, reads its rooms, renders a list. No auth logic, no patch logic.
  • System browser only for OAuth (ASWebAuthenticationSession, not WKWebView).
  • Keychain for the session (never UserDefaults/plist).
  • Publishable key only client-side. The service_role/secret key never ships in a client.
  • Open string unions for any auth/pairing errorCode the client renders — unknown values degrade to a neutral message.
  • New providers / pairing changes ship server-side (Supabase + relay) — no native release required.

8. Project values (public)

Project URLhttps://smafvhchpoumnpobqenq.supabase.co
Publishable key (safe to ship in clients)sb_publishable_CEHMCHr82txYNktovte7DA_kgmZa-qr
OAuth callbackhttps://smafvhchpoumnpobqenq.supabase.co/auth/v1/callback (optionally https://auth.vsrelay.dev/auth/v1/callback once the custom domain is set)
App origin (prod)https://app.vsrelay.dev (PWA, live on Vercel; apex vsrelay.dev serves the marketing site)

9. Built vs planned

  • Built (master): schema profiles/rooms/devices + RLS + handle_new_user; slim PWA client (@supabase/auth-js + @supabase/postgrest-js, exports auth/db/setDbAuth); migrations CI; GitHub + Google providers live.
  • 🔨 Planned: PWA login gate (AuthGate + LoginScreen) → account pairing (PairingScreen + pairWithRoom) → extension vsrelay.signIn (registers its room) → relay Option-A sync.

References

  • Spec: docs/superpowers/specs/2026-05-24-vsrelay-phase2-supabase-auth-design (source repo)
  • Plans: docs/superpowers/plans/2026-05-24-vsrelay-phase2-fundament, …-pwa-oauth-login (source repo)
  • Reference renderer: packages/web/src/core/supabase.ts, components/AuthGate.tsx, components/LoginScreen.tsx
  • Relay contract: Relay · Trust tiers: ProductionDeploymentArchitecture