Overview
Auth is handled by Supabase and enforced in Next.js via middleware. Public marketing/docs live in (app), while dashboard, projects, settings, and playground are protected. Auth pages are grouped under (auth-pages) (sign-in, sign-up, reset-password).
Routes & protection
- Public: app/(app)/*
- Auth: app/(auth-pages)/sign-in, sign-up, reset-password
- Protected: app/dashboard/, app/projects/, app/settings/, app/playground/
- robots.ts disallows /auth*, /dashboard*, and /api/*
Protected routes
Route protection runs inside proxy.ts — a feature-flag-aware proxy that sits in front of Next.js. It checks the Supabase session via createServerClient. If no user is found on a protected path, it redirects to /sign-in and preserves intent parameters (variant_id, redirect_to).
Protected paths: /dashboard/*, /projects/*, /settings/*, /playground/*.
// proxy.ts (simplified excerpt)
import { createServerClient } from '@supabase/ssr'
async function handleAuthProtection(req: Request) {
const url = new URL(req.url)
const supabase = createServerClient({ cookies: { /* read/write */ } })
const { data: { user } } = await supabase.auth.getUser()
if (user) return NextResponse.next()
const signin = new URL('/sign-in', url.origin)
signin.search = url.search // keeps ?variant_id=&redirect_to=
return NextResponse.redirect(signin)
}The proxy also handles maintenance mode and auth-gating when ENABLE_AUTH=false, before Next.js ever processes the request.
Auth pages & layout
Sign-in, Sign-up, and Reset Password share a reusable AuthLayout with consistent transitions. Errors are shown via ActionMessageToast. Social buttons are rendered from a small map using an icon button variant to keep styling consistent.
Social logins
Providers: Google, GitHub, Apple, Facebook.
- Configure OAuth apps in the Supabase dashboard.
- Add redirect URLs for both local and production.
- Expose only the anon key to the client; keep service role on the server.
Add provider callback URLs for each environment in Supabase → Authentication → Providers.
Subscribe flow
If an unauthenticated user clicks Subscribe, send them to sign-up with ?variant_id=<polar-product-id>&redirect_to=/checkout. When toggling between Sign In and Sign Up, always carry these params so purchase intent survives. After successful auth, /auth/callback reads the params and hands off to the Polar checkout flow via SubscribeRedirector.
// Example link that keeps current params
<Link href={{ pathname: '/auth/sign-up', query: Object.fromEntries(new URLSearchParams(search)) }}>Sign up</Link>Always preserve redirect_to and variant_id when switching between auth screens.
Security notes
- Service role key is server-only (webhooks, admin actions).
- RLS ensures users can read/update only their own rows.
- Do not index auth pages;
robots.tsalready disallows /auth and /dashboard.
Never expose the service role key to the browser or client bundles.
Local development
- Add env to
.env.local:NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY(server-only)NEXT_PUBLIC_APP_URL(used by redirects and SEO)
- Set Site URL and Redirect URLs in Supabase Auth settings for localhost and production.
- Limit the middleware matcher to protected paths only.
- Test the subscribe flow end-to-end with
product_idandredirect_to.