auth · tutorial
Protect Pages AND APIs: Your Locked Page Leaks
Pages redirect humans usefully and APIs return codes clients handle, both from one guard. The two-presentation paywall pattern.

The page is locked but the API is wide open
You guard the dashboard beautifully. Free users bounce to pricing. Strangers go to login.
Then someone opens devtools, calls the export API directly and downloads premium data with a free account.
Or the reverse.
Every API returns 401 correctly, but the dashboard renders its charts first and flashes "upgrade" after a second of premium content.
Enough for scrapers.
Embarrassing for everyone.
One rule, two presentations.
Humans get redirects.
Machines get codes.
Both from the same guard.
Lock one layer and attackers use the other.
One layer or two
| Attack or UX | Pages only guarded | APIs only guarded | Both |
|---|---|---|---|
| Direct API call, free account | Full data leak | Blocked | Blocked |
| Page visit, free account | Redirected | Content flashes, then blocks | Redirected, nothing renders |
| Logged-out API call | A 200, a leak | 401 | 401 |
| Logged-out page visit | A sign-in redirect | A blank 401 page | A sign-in redirect |
| An agent-added surface | A coin flip | A coin flip | Guarded, by a review rule |
Single-layer protection fails structurally. Three reasons.
Pages are suggestions. APIs are the asset. Everything a page displays arrives through some data path. A guarded page plus an open API means the data walks out the side door. Attackers never click. They request.
A content flash leaks and embarrasses. Client-side checks render premium UI before the verdict arrives. Server-side layout guards render nothing until decided.
Error codes are a language. Humans need destinations. Machines need numbers. One layer serving both speaks neither well.
What to do
Guard layouts on the server, before anything renders.
A stranger is sent to sign in. A free user is sent to pricing, not to a dead-end wall. A paid user sees the page.
Server Components decide before streaming a single byte. No premium markup for scrapers. No flash for users.
Guard APIs with codes, from the same guard.
A visitor who is not signed in gets a 401: log in. A signed-in visitor who has not paid gets a 403: pay.
Clients respond correctly instead of guessing from an HTML page.
Enforce both in review.
The rule names both halves, because agents guard whichever half the prompt mentioned and forget the other. A new paid page needs a layout guard. A new paid endpoint needs a code guard.
On audit day, search both trees for paid surfaces that do not call the guard.
The two-front audit
| Probe | Pages only guarded | Both guarded |
|---|---|---|
| Direct API call, free account | Full data leak | Blocked with a 403 |
| Page visit, free account | Redirected | Redirected, nothing renders |
| Logged-out API call | A 200, a leak | A 401 |
| Logged-out page visit | A sign-in redirect | A sign-in redirect |
| A new agent-added surface | Ships open | Blocked in review |
Probe quarterly with a command-line request and two browsers, one as a stranger and one as a free user.
The command-line probes matter most. They test what attackers test. Audit the adversary's path, not just the customer's.
The tax
Double guarding doubles the call sites. A layout plus a route per feature. Each check costs one user lookup per navigation.
A leaked export costs far more than a year of indexed reads. And the second guard is a template paste, not design work.
Know this first
Double guarding doubles the call sites. Each check costs one user lookup per navigation.
A leaked export costs far more.
Why BoringPush is the way to do this
Lock both doors.
One guard serves pages and APIs. Pages redirect people. APIs return a code.
The check comes first in every protected route.
FAQ
Should I use middleware instead of layouts?
Use middleware for coarse routing, such as separating public pages from the signed-in area, and layouts for decisions. Layouts have cheap database access that middleware lacks. Middleware guessing access without user records duplicates logic and drifts.
What about client components that need access state?
Resolve the guard once on the server and pass the result down as props. Client components never call the guard and never re-decide. One server verdict per navigation leaves nothing to drift and nothing to flash.
How do I handle the loading state between click and redirect?
There is none for server guards, because they redirect before anything streams. For client-initiated actions, the client handles a 403 with an upgrade prompt rather than a page load.
Do static or cached pages need layout guards?
Yes, if the content is paid. Make paid paths render per request, so the guard runs every time and no single verdict gets baked into cached HTML. A cached premium page served to a free user is a leak with a timestamp.
How do Server Actions fit?
Actions are APIs in function form. Return codes or objects, never access redirects, and guard on the first line with the same guard. The transport varies, but the guard never notices.
What breaks when teams guard only new surfaces?
Old surfaces predate the convention and hide the original holes. Run one backfill audit across both trees plus the command-line probes, then let the review rule hold the line.
Keep reading
- requirePaidUser(): One Boolean, Zero Paywall Holes — the guard both fronts share
- Dev Login Trick: Test Paywalls in 1 Click, Not 4 — the matrix proving both fronts
- Auth Without the 2 a.m. Pages: Google + Magic Links — the founding sketch this pattern hardens
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


