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.ts already disallows /auth and /dashboard.

Never expose the service role key to the browser or client bundles.

Local development

  1. Add env to .env.local:
    • NEXT_PUBLIC_SUPABASE_URL
    • NEXT_PUBLIC_SUPABASE_ANON_KEY
    • SUPABASE_SERVICE_ROLE_KEY (server-only)
    • NEXT_PUBLIC_APP_URL (used by redirects and SEO)
  2. Set Site URL and Redirect URLs in Supabase Auth settings for localhost and production.
  3. Limit the middleware matcher to protected paths only.
  4. Test the subscribe flow end-to-end with product_id and redirect_to.