ログインは問題なくできたのに、画面に他のユーザーの注文・プロフィール・メモが自分のものと混ざって表示されています。これは情報漏えいなので、最優先で直すべきバグです。原因はほぼ毎回同じで、サーバーが「この行は誰のものか」でフィルタリングせず、すべてをブラウザに送ってしまっているのです。
Does this match your symptom?
- 自分のアカウントでログインしているのに、一覧に他人のデータが含まれている
- URL の数値
idを変えると他人の詳細ページが開く - 最初は問題なかったが、利用者が増えたらお互いのデータが見えるようになった
Why it happens
開発者でない方向けに、よくある原因は3つです:
- Supabase の RLS(Row Level Security)がオフになっている —
publicスキーマのテーブルは、RLS を有効にしない限り API 経由で丸ごと読み取れてしまいます。 - クエリに「自分のものだけ」という条件がない —
WHERE user_id = ...が抜けている、または Firebase のルールがテストモード(if true)のまま。 - React(クライアント側)だけでフィルタリングしている — 他人の分も含めて全行がすでにブラウザに届いていて、画面上で隠しているだけです。DevTools の Network タブを開けば全部見えます。This is not security.
30-second diagnosis
ブラウザで:F12 → Network tab → 一覧を読み込むリクエストをクリック → Response。
JSON のレスポンスに他ユーザーの行が含まれていたら、サーバーが送っているということなので、サーバー側で直す必要があります(下記)。React だけを直しても解決しません。
Fix A — If you use Supabase
- テーブルに
user_idカラムがあることを確認します(なければauth.usersの id を保持するuuidカラムを追加)。 - ダッシュボードの SQL Editor で以下を実行します(
todosは自分のテーブル名に置き換え):
-- 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 );
- 保存して、別のアカウントでログインし直して再テストします。これで他ユーザーの行はレスポンスから完全に消えます。
> 注意:RLS を有効にすると、ポリシーを追加するまでは何も見えなくなります — これは想定どおりです。上のポリシーが「自分のものだけ」を復活させます。
Fix B — If you use Firebase (Firestore)
Firebase コンソールで Firestore Database → Rules を開き、テストモードのルール(allow read, write: if true;)を所有者ルールに置き換えて、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;
}
}
}
さらに、クエリ自体も自分のデータだけに絞り込む必要があります。Firestore のルールはフィルターではありません:広すぎるクエリは黙って絞られるのではなく、そのまま拒否されます。
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);
(データがドキュメントパスにユーザー id を含む形なら、match /users/{userId} に allow read, write: if request.auth.uid == userId; を使います。)
Fix C — If you have your own backend (Express, etc.)
ログインのセッション/トークンから取り出した userId でクエリを強制します。Never trust an id sent by the client — すり替えられる可能性があります。
// 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
クライアント側(React)のフィルターは画面を整えるためのもので、セキュリティのためではありません。実際に漏えいを止められるのは サーバー(RLS、セキュリティルール、WHERE) だけです。
