Live demo — shared accounts, sample data, reset regularly. Don't enter anything real.Get the source

Recipe: Add a protected page

Recipe: Add a protected page

There are two layers to wire (see architecture.md for why both exist) — skipping the second one is the classic mistake, since the first layer alone is not real authorization.

  1. Create the page under the right route group:

    • Any signed-in user → app/(app)/your-page/page.tsx
    • Paid/trialing users only → same group, call requireAccess()
    • Admins only → app/(admin)/admin/your-page/page.tsx
  2. Call the matching DAL function at the top of the page (or its layout, if every page under it needs the same gate):

    import { verifySession } from "@/lib/dal"; // any signed-in user
    // or: import { requireAccess } from "@/lib/dal";   // paid tier
    // or: import { requireAdmin } from "@/lib/dal";    // admin only
     
    export default async function YourPage() {
      const { user } = await verifySession();
      // ...
    }

    This is the real check — it runs server-side on every request and redirects (verifySession/requireAccess) or 404s (requireAdmin) before any of your page's logic runs.

  3. Add the path prefix to proxy.ts — this is only a fast, cookie-based redirect for UX (avoids a round-trip to render a page that will just redirect), so it can lag slightly, but keep it in sync:

    const PROTECTED_PREFIXES = [
      "/dashboard",
      "/billing",
      "/settings",
      "/subscribe",
      "/admin",
      "/your-page",   // add this
    ];

    and add the matching entry to the config.matcher array below it.

  4. Verify: log out and visit the URL directly — you should land on /login instantly (proxy) even before hitting the DAL check.

Server Actions need the same treatment: call verifySession() / requireAccess() / requireAdmin() as the first line of the action function itself — Server Actions aren't covered by proxy.ts at all.