ログインは問題なくできたのに、画面に他のユーザーの注文・プロフィール・メモが自分のものと混ざって表示されています。これは情報漏えいなので、最優先で直すべきバグです。原因はほぼ毎回同じで、サーバーが「この行は誰のものか」でフィルタリングせず、すべてをブラウザに送ってしまっているのです。

Does this match your symptom?

  • 自分のアカウントでログインしているのに、一覧に他人のデータが含まれている
  • URL の数値 id を変えると他人の詳細ページが開く
  • 最初は問題なかったが、利用者が増えたらお互いのデータが見えるようになった

Why it happens

開発者でない方向けに、よくある原因は3つです:

  1. Supabase の RLS(Row Level Security)がオフになっているpublic スキーマのテーブルは、RLS を有効にしない限り API 経由で丸ごと読み取れてしまいます。
  2. クエリに「自分のものだけ」という条件がないWHERE user_id = ... が抜けている、または Firebase のルールがテストモード(if true)のまま。
  3. React(クライアント側)だけでフィルタリングしている — 他人の分も含めて全行がすでにブラウザに届いていて、画面上で隠しているだけです。DevTools の Network タブを開けば全部見えます。This is not security.

30-second diagnosis

ブラウザで:F12 → Network tab → 一覧を読み込むリクエストをクリック → Response

JSON のレスポンスに他ユーザーの行が含まれていたら、サーバーが送っているということなので、サーバー側で直す必要があります(下記)。React だけを直しても解決しません。

Fix A — If you use Supabase

  1. テーブルに user_id カラムがあることを確認します(なければ auth.users の id を保持する uuid カラムを追加)。
  2. ダッシュボードの SQL Editor で以下を実行します(todos は自分のテーブル名に置き換え):
sql

-- 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 );
  1. 保存して、別のアカウントでログインし直して再テストします。これで他ユーザーの行はレスポンスから完全に消えます。

> 注意:RLS を有効にすると、ポリシーを追加するまでは何も見えなくなります — これは想定どおりです。上のポリシーが「自分のものだけ」を復活させます。

Fix B — If you use Firebase (Firestore)

Firebase コンソールで Firestore Database → Rules を開き、テストモードのルール(allow read, write: if true;)を所有者ルールに置き換えて、Publish します:

text

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 のルールはフィルターではありません:広すぎるクエリは黙って絞られるのではなく、そのまま拒否されます。

js

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 — すり替えられる可能性があります。

js

// 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) だけです。