You logged in fine, but the screen shows other users' orders, profiles, or notes mixed in with yours. This is a data leak, so it's the highest-priority bug to fix. The cause is almost always the same: the server isn't filtering by "who owns this row" and is sending everything to the browser.
Does this match your symptom?
- You're logged into your own account but the list contains other people's data
- Changing the numeric
idin the URL opens someone else's detail page - It was fine early on, but once you had more users they could see each other's data
Why it happens
Three common causes for non-developers:
- Supabase RLS (Row Level Security) is off — tables in the
publicschema are readable through the API in full unless RLS is enabled. - The query has no "only mine" condition — a missing
WHERE user_id = ..., or Firebase rules left in test mode (if true). - Filtering only in React (client-side) — all rows, including other people's, already reached the browser and you just hid them on screen. Open the DevTools Network tab and they're all there. This is not security.
30-second diagnosis
In the browser: F12 → Network tab → click the request that loads the list → Response.
If the JSON response contains other users' rows, the server is sending them, so you must fix it on the server (below). Editing React alone will not fix it.
Fix A — If you use Supabase
- Make sure the table has a
user_idcolumn (add auuidcolumn holding theauth.usersid if it's missing). - Run this in the dashboard SQL Editor (replace
todoswith your table name):
-- 1) Turn RLS on
alter table todos enable row level security;
-- 2) Auto-fill the owner id on new rows (optional)
alter table todos alter column user_id set default auth.uid();
-- 3) Read only my rows
create policy "select own rows" on todos
for select to authenticated
using ( auth.uid() = user_id );
-- 4) Insert only as myself
create policy "insert own rows" on todos
for insert to authenticated
with check ( auth.uid() = user_id );
-- 5) Update only my rows
create policy "update own rows" on todos
for update to authenticated
using ( auth.uid() = user_id )
with check ( auth.uid() = user_id );
-- 6) Delete only my rows
create policy "delete own rows" on todos
for delete to authenticated
using ( auth.uid() = user_id );
- Save, log in as a different account, and retest. Other users' rows now drop out of the response entirely.
> Note: once RLS is on, nothing is visible until you add policies — that's expected. The policies above are what restore "only mine."
Fix B — If you use Firebase (Firestore)
In the Firebase console go to Firestore Database → Rules, replace the test-mode rule (allow read, write: if true;) with the owner rule, and Publish:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /todos/{docId} {
allow read, update, delete: if request.auth != null
&& request.auth.uid == resource.data.uid;
allow create: if request.auth != null
&& request.auth.uid == request.resource.data.uid;
}
}
}
You must also filter the query to your own data. Firestore rules are not filters: a broad query isn't silently trimmed — it's rejected outright.
import { collection, query, where, getDocs } from "firebase/firestore";
const q = query(
collection(db, "todos"),
where("uid", "==", auth.currentUser.uid) // only mine
);
const snap = await getDocs(q);
(If your data is keyed by user id in the document path, use match /users/{userId} with allow read, write: if request.auth.uid == userId;.)
Fix C — If you have your own backend (Express, etc.)
Force the query with the userId taken from the login session/token. Never trust an id sent by the client — it can be swapped.
// use the value from the session only
const rows = await db.query(
"select * from todos where user_id = $1",
[req.user.id] // NOT req.body / req.query id
);
The one line to remember
A client-side (React) filter is for tidying the screen, not for security. Only the server (RLS, security rules, WHERE) actually stops the leak.
