Logged-In Is Not Allowed
Your app checks that somebody is signed in. That is a different question from whether this particular record is theirs — and most apps only ask the first.
A login tells you who is knocking. It does not tell you which door they are allowed to open.
one-time · instant download · yours to keep
01 — The problem
It works perfectly right up until you have two customers.
Two questions, one asked
Who are you, and may you have this one. The first is what makes the app work, so that is the one that gets built. The second only matters once somebody else's data is in the same table.
Nothing looks wrong
You are signed in. The page loads. It shows the right thing, because you asked for your own. The failure only appears when somebody asks for a record that is not theirs, and nobody tests that.
The fix is one line you were never told to write
Comparing the record to the person asking. It is not difficult and it is not expensive. It is just invisible until you know to look for it, which is the entire problem.
02 — What's inside
Four questions per route. None of them an opinion.
What it touches
Can this route reach a record belonging to somebody else, or only this person's own and things the whole account is meant to see?
What it checks
Two separate answers: is there anything stopping a stranger with no account, and does it compare the record to the person asking.
Where the identifier comes from
From the session, or from the request — and if from the request, is it a long random string or a number that runs 1, 2, 3.
A route is anything your app does on request that can reach stored data. A page that loads a record, a button that saves or deletes, a download link. If your builder calls them endpoints, functions, or actions, those are the same thing.
03 — The standard
Three rules it will not bend for you.
A login can never on its own read AUTHORIZED
On any route that can reach somebody else's record, checking that a person is signed in is not an answer to whether this record is theirs. Those rows read LOGGED-IN ONLY however carefully the login is built, and the only thing that clears them is a check that compares the record to the person asking.
A login anyone can walk past by counting is not a login
If a route only checks that you are signed in, and takes the record's identifier from the request as a number that runs 1, 2, 3, then signing up is free and the account requirement buys nothing. Somebody makes an account, changes the number, and walks the table. Those rows drop to NOBODY IS CHECKING even though there is a login in front of them.
Some routes need nothing more than an account, and are told to stay that way
Opening your own dashboard, posting feedback, reading a team directory. Those read MEMBERSHIP IS ENOUGH and are never counted against you. Demanding an ownership check where there is no owner to check against would be inventing work, and a tool that flags everything trains you to flag nothing.
04 — How it works
An afternoon, and one habit you keep.
01
List every route. Anything your app does on request that can reach stored data — pages that load records, buttons that save or delete, download links.
02
Mark which ones can reach a record belonging to somebody else. That single column decides which rows the rest of the questions even apply to.
03
Answer the three remaining questions from your own project, using the plain-words guide. Answer cant_find honestly where you have to.
04
Take the fix-first route to whatever you build with. Fix that one, read the row again, watch the verdict move.
05 — What you'll see
The worked example, live.
Ten routes from a typical first app. Look at R-04 and R-05: the same document, the same login, the same missing ownership check. One of them anybody can walk through by counting, and the only thing that differs is where the identifier comes from.
Try it on the worked example
Ten routes from a typical first app. Compare R-04 and R-05: same route, same login, same missing ownership check — and one of them anybody can walk through by counting.
| Route | Touches | Signed-in check | Ownership check | Record id from | Destination |
|---|---|---|---|---|---|
R-01 Open the dashboard | MEMBERSHIP IS ENOUGH it only ever reaches this person's own data or something the whole account is meant to see, so being signed in is the permission | ||||
R-02 Download an invoice PDF | AUTHORIZED it checks that the record belongs to the person asking | ||||
R-03 View a customer order | NOBODY IS CHECKING nothing checks whether the caller is signed in | ||||
R-04 Edit a saved document | LOGGED-IN ONLY it checks that somebody is signed in and stops there, on a route that can reach another person's record | ||||
R-05 Delete a saved document | NOBODY IS CHECKING it checks that somebody is signed in and takes the record's identifier from the request as a number anyone can count through - signing up is free, so the account requirement buys nothing | ||||
R-06 Open a saved document | AUTHORIZED it checks that the record belongs to the person asking | ||||
R-07 Submit feedback | MEMBERSHIP IS ENOUGH it only ever reaches this person's own data or something the whole account is meant to see, so being signed in is the permission | ||||
R-08 Admin list of all users | LOGGED-IN ONLY it checks that somebody is signed in and stops there, on a route that can reach another person's record | ||||
R-09 Change account email | AUTHORIZED it checks that the record belongs to the person asking | ||||
R-10 Read the team directory | NOBODY IS CHECKING nothing checks whether the caller is signed in |
Verdict
ANYONE CAN WALK IN
At least one route has nothing standing in the way.
Fix first R-10 — nothing checks whether the caller is signed in
5 of 10
routes that need work
50% of the routes listed
3
have a signed-in check and are still open
a login in front of it, and still open
1
anyone can walk through by counting
signing up is free
2
told to leave alone
an account is the whole permission
None of the four numbers above changes the verdict. Routes that read MEMBERSHIP IS ENOUGH are never counted against you.
This is the live engine. 80 rows, the same formulas, in a file you keep Start Here tab that explains every answer in plain words Dashboard with the verdict, the counts, and what to fix first
Get the kit — $5906 — Who it's for
For the person who added accounts and thought that was the hard part.
Buy this if
- More than one person has an account on something you built with an AI tool.
- You added a login and were never told there was a second check.
- Your URLs have numbers in them and you have never thought about that.
- Somebody asked whether a user can open another user's record and you paused.
Skip it if
- You build authorisation systems for a living. There is nothing here you do not know.
- Your app has one account, and it is yours.
- You want something that probes your live routes — this is a read-off, not a scanner.
What this is not
It scans nothing, connects to nothing, and never sees your code, your keys, or your database. It grades how an app is built and never a person. It is not a scanner, a penetration test, or a code audit, and it is not legal advice — it reads back what you tell it, using rules you can see in the formulas.
07 — Works alongside
Where this sits.
Three layers, three different questions. This one is the middle: on the routes your app does guard, is it checking the right thing?
The Browser Line Read-Off
$59The keystone of the same line. It asks where each decision is made; this one asks whether the decision being made is the right one.
The Supabase RLS Read-Off
$69The database layer. A route can run on your server against a properly locked table and still hand over the wrong record — these two cover different halves of that.
Vibe-Coded App Pre-Launch Security Gate
$79The launch-moment go/no-go across six controls. One of them is server-side auth on every sensitive route; this is how you answer it from evidence.
08 — Common questions
Before you buy.
It is half of it. A login answers who somebody is. It says nothing about whether this particular record is theirs. Those are two different questions and most apps built in a hurry only ask the first, because asking the first is what makes the app work — the second only matters once there is more than one customer, which is exactly when nobody is looking.
No. It connects to nothing and uploads nothing. You look at your own routes, answer four questions about each one, and the spreadsheet does the rest. Every answer is something you go and look at rather than something you judge.
Because a login only slows somebody down if they cannot guess what to ask for. If a route checks that you are signed in and takes the record's identifier from the address bar as a number that runs 1, 2, 3, somebody makes a free account, changes the number, and walks the whole table. Those rows read NOBODY IS CHECKING even though there is a login in front of them, because in practice there is not.
No, it means leave it alone. Plenty of routes need nothing more than an account — listing the plans available to you, posting feedback, opening your own dashboard. Demanding an ownership check on all of them would be inventing work, and a tool that flags everything trains you to flag nothing. Those rows are never counted against you.
Answer cant_find. It is scored on the unsafe side, deliberately — you do not get credit for a check you cannot find. That will make your result look worse than it may really be, and the fix is to go and find the answer. The dashboard counts those separately so you can see how much of your result is broken versus not yet looked at.
The Browser Line Read-Off asks where each decision is made. The Supabase RLS Read-Off asks what the database itself allows. This one sits between them and asks a question neither covers: on the routes your app does guard, is it checking the right thing? A route can run entirely on your server, against a properly locked table, and still hand over somebody else's record because it only ever checked that you were signed in.
Get the Read-Off
Find out which routes check the right thing.
- One .xlsx — Start Here, Dashboard, Read-Off
- 80 rows with dropdowns and live formulas
- A worked ten-route example, already filled in
- Offline forever — nothing uploaded, no access granted