로그인은 잘 됐는데 화면에 다른 사람의 주문·프로필·메모가 같이 보이는 문제입니다. 개인정보 유출이라 가장 먼저 막아야 하는 버그예요. 원인은 거의 항상 하나입니다. 서버가 "누구 데이터인지"를 안 걸러주고 통째로 브라우저에 내려보내는 것입니다.
이런 증상이면 여기 해당
- 내 계정으로 로그인했는데 목록에 남의 데이터가 섞여 나온다
- 주소창의 숫자
id를 바꾸면 남의 상세 페이지가 열린다 - 처음엔 괜찮다가 사용자가 늘자 서로의 데이터가 보인다
왜 생기나 (원인)
비개발자가 자주 밟는 세 가지입니다.
- Supabase에서 RLS(행 수준 보안)가 꺼져 있음 —
public스키마의 테이블은 RLS가 없으면 API로 통째로 읽힙니다. - 쿼리에 "내 것만" 조건이 없음 —
WHERE user_id = ...가 빠졌거나, Firebase 규칙이 테스트 모드(if true)로 열려 있음. - React에서만 걸러냄(클라이언트 필터) — 이미 남의 데이터까지 브라우저로 다 내려온 뒤 화면에서만 숨긴 것. F12 네트워크 탭을 열면 다 보입니다. 이건 보안이 아닙니다.
먼저 30초 진단
브라우저에서 F12 → Network 탭 → 목록을 불러오는 요청 클릭 → Response 확인.
응답(JSON) 안에 남의 row가 들어 있으면 → 서버가 다 보내는 것이므로 아래처럼 서버에서 막아야 합니다. (React 코드만 고쳐선 못 막아요.)
해결 A — Supabase를 쓴다면
- 테이블에
user_id컬럼이 있는지 확인하세요. 없으면uuid타입으로 추가하고auth.users의 id를 담습니다. - 대시보드의 SQL Editor에서 아래를 실행하세요. 테이블명
todos는 본인 것으로 바꾸면 됩니다.
-- 1) RLS 켜기
alter table todos enable row level security;
-- 2) 새 행에 내 id가 자동으로 들어가게(선택)
alter table todos alter column user_id set default auth.uid();
-- 3) 내 데이터만 조회
create policy "select own rows" on todos
for select to authenticated
using ( auth.uid() = user_id );
-- 4) 내 이름으로만 생성
create policy "insert own rows" on todos
for insert to authenticated
with check ( auth.uid() = user_id );
-- 5) 내 데이터만 수정
create policy "update own rows" on todos
for update to authenticated
using ( auth.uid() = user_id )
with check ( auth.uid() = user_id );
-- 6) 내 데이터만 삭제
create policy "delete own rows" on todos
for delete to authenticated
using ( auth.uid() = user_id );
- 저장한 뒤 다른 계정으로 로그인해 다시 테스트하세요. 이제 남의 데이터는 응답에서 아예 빠집니다.
> 참고: RLS를 켜면 정책을 만들기 전까지는 아무 데이터도 안 보입니다(정상). 위 정책까지 넣어야 "내 것만" 보입니다.
해결 B — 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) // 내 것만
);
const snap = await getDocs(q);
(프로필처럼 문서 경로에 사용자 id가 들어가는 구조라면 match /users/{userId}에 allow read, write: if request.auth.uid == userId;를 씁니다.)
해결 C — 직접 만든 백엔드(Express 등)라면
로그인 세션/토큰에서 꺼낸 userId로 쿼리를 강제하세요. 클라이언트가 보낸 id는 절대 믿지 마세요(바꿔치기 가능).
// 세션에서 온 값만 사용
const rows = await db.query(
"select * from todos where user_id = $1",
[req.user.id] // req.body / req.query 의 id 가 아니라!
);
꼭 기억할 한 줄
클라이언트(React) 필터는 화면 정리용이지 보안이 아닙니다. 서버(RLS·보안 규칙·WHERE)에서 막아야 진짜로 막힙니다.
