Auth pattern — Supabase Auth + RLS
Why Supabase Auth (not Clerk / NextAuth / Auth0)
- Same vendor as DB → JWT
sub↔auth.users.idjoin is native (no email-lookup roundtrip) - RLS lives next to the data — auth and authz are one table read away
- Magic link + Google + GitHub out of the box
- Free up to 50k MAU; matches our pre-revenue runway
RLS policies (per role)
sql-- Tenant isolation: every table has user_id or organization_id
CREATE POLICY "own rows only" ON pages
FOR SELECT USING (
organization_id IN (
SELECT organization_id FROM organization_members
WHERE user_id = auth.uid()
)
);
Editors get write access. Viewers get read-only. Admin role bypasses via service-role key on the server only — never in the browser.
Session handling
- Server components →
createServerClientwith cookie store - Client components →
@supabase/ssrbrowser client - API routes → verify the JWT with
verifyAuthToken(seelib/verify-auth.ts) - Anonymous edits → cookie-grouped
anonymous_iduntil first sign-in
Magic link tips
- Email template lives in Supabase dashboard, not in code (annoying for PRs)
- 24h link TTL; user pasting into wrong browser → friction. Use OTP fallback when telemetry shows >5% magic-link bounces.
Open questions
- Org invitations: do we want self-serve invite links or just email invitations?
- Should we move to passkeys before launch or after?