
NEXT_PUBLIC_ 환경변수는 비밀이 아닙니다. Supabase anon key는 개발자 도구 네트워크 탭에서 누구나 볼 수 있습니다.auth.uid()로 로그인 유저를 기준으로 필터링합니다.SELECT 정책이 없으면 데이터가 빈 배열로 옵니다.주니어 개발자가 흔히 하는 오해 중 하나는 "프론트엔드의 .env 파일에 적어둔 환경변수는 비밀이다"라는 생각입니다. 하지만 Next.js에서 NEXT_PUBLIC_ 접두사를 붙이는 순간, 그 값은 빌드 타임에 자바스크립트 코드 안에 평문으로 박힙니다.
사용자가 사이트에 접속해 개발자 도구(F12)를 열고 네트워크 탭만 봐도, Supabase와 통신할 때 쓰는 anon key가 그대로 노출됩니다. 즉, 전 세계 누구나 여러분의 DB API 주소와 키를 알 수 있다는 뜻입니다. 아무런 조치도 하지 않았다면, 누군가 이 키로 DB 데이터를 삭제하거나 남의 비공개 글을 열람할 수 있습니다.
선택지는 두 가지입니다.
Supabase의 철학을 살리면서도 보안을 챙기는 정석적인 방법은 RLS를 활성화하는 것입니다.
RLS는 행 수준 보안(Row Level Security)의 약자로, 테이블 전체에 대한 권한이 아니라 데이터 한 줄(Row)마다 개별 보안 규칙을 적용하는 기술입니다.
auth.uid())를 기준으로 필터링합니다.가장 먼저 해당 테이블의 RLS를 켜야 합니다. 기본적으로 꺼져 있으면 무법지대입니다.
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
블로그 포스트나 댓글 시스템에서 가장 흔히 쓰이는 방식입니다.
-- 1. 모두에게 읽기 권한 허용 CREATE POLICY "전체 읽기 허용" ON posts FOR SELECT USING (true); -- 2. 로그인한 작성자에게만 수정 권한 허용 CREATE POLICY "작성자만 수정 가능" ON posts FOR UPDATE USING (auth.uid() = author_id);
남의 이름으로 글을 쓰는 것을 방지합니다.
CREATE POLICY "본인 글만 작성 가능" ON posts FOR INSERT WITH CHECK (auth.uid() = author_id);
RLS를 켜고 데이터를 조회했는데 빈 배열([])이 온다면, 해당 유저에게 허용된 Policy가 하나도 없기 때문입니다. RLS는 "허용되지 않은 모든 것은 거부한다"는 원칙으로 동작하므로, 반드시 SELECT 정책을 명시적으로 추가해야 합니다.
"API 키는 공개되어 있다. 그러니 보안은 DB 내부에서 챙긴다." 이것이 Supabase 환경에서의 올바른 보안 마인드셋입니다.
환경변수에 anon key를 넣는 것은 보안을 위해서가 아니라 관리를 위해서라는 점을 기억하면 좋겠습니다. 진짜 보안은 정성스럽게 작성한 RLS Policy 한 줄에서 시작합니다. 지금 여러분의 Supabase 대시보드에서 RLS가 켜져 있는지 확인해 보세요.