Demo implementation. The auth setup here is intentionally minimal to keep the reference app simple to fork and run. It is not a production-ready auth system. A real deployment should add: email/password or social sign-in, account recovery, session management UX, and CAPTCHA or abuse protection. Supabase Auth supports all of these — this app deliberately omits them to stay focused on the Cloudinary integration.
Authentication is handled by Supabase Auth via @supabase/ssr. The app uses anonymous (guest) sign-in — no email, no password, no SMTP configuration required. Users get a full Supabase session with a UUID instantly.
Trade-off to document for users: progress is saved in the browser cookie. Clearing cookies creates a new anonymous session with a new UUID; there is no account recovery path. This is intentional — the app is a reference/demo. Forkers who need persistence should add an account-linking flow (Supabase updateUser preserves the UUID).
Production scale note: anonymous sign-ins are rate-limited by Supabase per IP by default. For high-traffic deployments, consider adding CAPTCHA (Supabase Auth supports hCaptcha/Turnstile) and a scheduled cleanup job to delete inactive anonymous users — see the Supabase anonymous auth docs for current guidance on both.
- User clicks Play as guest at
/sign-in. playAsGuestserver action (app/(auth)/sign-in/actions.ts) callssupabase.auth.signInAnonymously().- On success, redirects to
/profile. - Profile page checks
getProfile()— if no profile row exists yet, redirects to/set-handle. - On failure (e.g. anonymous sign-ins not enabled in dashboard), returns
{ error: string }displayed on-screen.
Anonymous sign-ins must be enabled in the Supabase dashboard for each environment:
Dashboard → Authentication → Sign In / Providers → toggle "Allow anonymous sign-ins"
This is a per-project setting. Enable it for your local dev project and your production project separately.
Session cookies are managed by @supabase/ssr. The proxy.ts middleware runs on every request and calls supabase.auth.getClaims() to refresh the session cookie before it expires.
Server Components use lib/supabase/server.ts (cookie-based server client) — session is read from cookies, no round-trip to Supabase on every request.
lib/auth/getSession.ts is a thin helper that calls supabase.auth.getUser() and returns the user or null. All protected pages call this at the top.
app/(protected)/layout.tsx gate-keeps all routes under (protected)/. If no session exists, it redirects to /sign-in. Individual pages may further redirect to /set-handle if the user hasn't created a profile yet.
After signing in as a guest, users must choose a handle (unique lowercase alphanumeric, 1–30 chars). If getProfile() returns null, protected pages redirect to /set-handle. The setHandle action creates a profiles row linked to auth.users. Anonymous users get the same UUID-keyed profile as any other user — all RLS policies work identically.
components/SignOutButton.tsx is a form that calls the signOut server action (app/(auth)/sign-in/actions.ts), which calls supabase.auth.signOut() and redirects to /sign-in.