Live demo of the Whirlwind template, as shippedStripe test mode: card 4242 4242 4242 4242, any future date, any CVCGet Whirlwind →

Auth Overview

Magic-link sign-in, sessions, and the middleware that guards the dashboard.

What ships

Supabase Auth with magic links. There are no passwords to store and no OAuth apps to register before you can sign in. - Sign-in modal: components/marketing/ButtonSignin.jsx calls signInWithOtp - Link handling: app/auth/confirm exchanges the token, app/auth/callback finishes the session in the browser - Session: cookies, read on the server by lib/auth.js - Protected routes: middleware.js guards /dashboard/*

Flow

1. The user enters an email in the sign-in modal 2. Supabase emails a magic link 3. The link lands on /auth/confirm, which creates the session 4. The user is sent to the page they asked for (the next parameter) 5. On first sign-in, a public.users row and a public.usage row are created by database triggers, and any pending purchase for that email is claimed

Protected routes

middleware.js runs on every /dashboard request. Without a session it redirects to the home page with the sign-in modal open and the original path preserved. If Supabase keys are not configured it redirects too, instead of throwing.
// middleware.js (abridged)
export const config = { matcher: ["/dashboard/:path*"] };

function redirectToSignin(request) {
  const url = new URL("/", request.url);
  url.searchParams.set("signin", "1");
  url.searchParams.set("next", request.nextUrl.pathname);
  return NextResponse.redirect(url);
}

Reading the user

On the client, useSupabaseAuth() from components/providers/SupabaseAuthProvider.jsx gives you the session. In API routes, getAuthenticatedUser(req) and getAppUser() from lib/auth.js verify the token and load the public.users row.