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.

A uniformed doorman holding open a car door on a city street, 1921
A uniformed doorman holding open a car door on a city street, 1921 · Photo: National Photo Company Collection, public domain, via Wikimedia Commons

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

+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)

The Agent-First Shipping Kit

Command from your phone.

Built so autonomous AI agents build what you want without breaking what's already paying you.

$199

Get BoringPush

One-time payment · Lifetime updates · Unlimited projects.

Keep reading