Slices
Auth
Supabase sign-in, profiles and route gating, with a demo mode that needs no keys.The auth slice is the Supabase layer: email and password sign-in, a
profile per user, the session refresh that runs on every request, and an
Upstash rate limiter in front of it. No surface talks to Supabase without
it, so a composition that never needs an account (docs, store,
landing) ships none of this code and none of its dependencies.
You rarely name it yourself. dashboard requires orgs, and orgs
requires auth, so the scaffolder pulls it in transitively.
Last updated October 18, 2026- src/lib/sb/ — browser, server and service-role clients, the config checks, safeNextPath.
- src/components/UserProvider.tsx — useUser(): profile, email, global role, refresh.
- src/app/(auth)/ — /auth (log in, sign up, forgot password) and /auth/update-password.
- src/app/api/profile/ — GET and PATCH the profile, POST /api/profile/avatar.
- src/proxy.ts — rate limiting, the session refresh and route gating.
- supabase/init.sql — profiles, roles, user_global_roles, the sign-up trigger, the profiles bucket.
/app/acme/settings -> /auth?next=%2Fapp%2Facme%2Fsettings
The forms send the user back to next after signing in, through
safeNextPath, which only honours a plain same-site path so the sign-in
page cannot become an open redirect. With no next, sign-in lands on
/app, or on / in a composition without the dashboard.
The proxy answers one question: is there a session. Whether that session
belongs in a particular organization needs the database, and is the job of
the organization guards.
The rate limit is a sliding window of 30 requests a minute per IP and path.
A refusal is a 429 in the same { error: { code, message } } shape the
route handlers use, with retry-after set.
The trigger in init.sql creates a profiles row and a member global
role for every sign-up. API routes act through requireUserAndService(),
which resolves the signed-in user and returns a service-role client: RLS is
enabled with no policies, so ownership is enforced in the route by
filtering on the user's id. The global role (admin, moderator,
member…) is about the person across the whole app, not about any team.
"Forgot password" calls resetPasswordForEmail with a redirect to
/auth/update-password. Add that URL to the redirect URLs in the Supabase
dashboard. The email signs the user in with a recovery session, so the
page only has to call updateUser({ password }).
The slice assumes the Supabase project belongs to this app alone. An app
joining a project other products already use must not run init.sql as
it is:
- Prefix every table, bucket and function with the app's name.
- Skip the trigger on auth.users, which would fire for every product's sign-ups, and create the per-user row lazily from the route that first needs it.
- Drop the role tables unless the product has a real admin surface.
Edit this page on GitHub