Skip to content

Supabase RLS 정책 작성 패턴 ​

RLS(Row Level Security)는 Supabase 보안의 핵심입니다. Claude Code에게 RLS 정책을 맡길 때는 테이블 구조, 인증 방식, 접근 규칙을 함께 명시해야 의도한 대로 작성해줍니다.


RLS를 Claude에게 맡길 때 흔한 실수 ​

auth.uid()와 일반 컬럼 혼동 Claude가 user_id = auth.uid() 대신 user_id = current_user처럼 잘못된 함수를 쓰는 경우가 있습니다. 반드시 Supabase auth 함수를 명시하세요.

정책 이름 없이 생성 이름 없는 정책은 나중에 수정하거나 삭제할 때 찾기 어렵습니다.

SELECT/INSERT/UPDATE/DELETE를 하나로 뭉치기 Supabase RLS는 작업별로 정책을 분리하는 게 관리하기 좋습니다.


📋 Prompt Template ​

아래 테이블에 대한 Supabase RLS 정책을 작성해줘.

[테이블 구조]
- 테이블명: typing_sessions
- 컬럼: id (uuid), user_id (uuid, FK → auth.users), content (text), created_at (timestamptz)

[인증 방식]
- Supabase Auth 사용
- 인증된 사용자: auth.uid() 함수로 현재 유저 ID 가져옴
- 비인증 사용자(anon): 접근 불가

[접근 규칙]
- SELECT: 자신의 레코드만 조회 가능 (user_id = auth.uid())
- INSERT: 인증된 사용자만 가능, user_id는 반드시 auth.uid()와 일치해야 함
- UPDATE: 자신의 레코드만 수정 가능
- DELETE: 자신의 레코드만 삭제 가능

[제약 조건]
- 정책 이름은 "[테이블명]_[작업]_[역할]" 형식으로 작성
- Supabase auth 함수 사용: auth.uid(), auth.role()
- 관리자 역할은 별도 처리하지 않음

RLS 정책 예시 (타이핑룸 기준) ​

sql
-- RLS 활성화
ALTER TABLE typing_sessions ENABLE ROW LEVEL SECURITY;

-- SELECT: 자신의 레코드만 조회
CREATE POLICY "typing_sessions_select_authenticated"
ON typing_sessions
FOR SELECT
TO authenticated
USING (user_id = auth.uid());

-- INSERT: 자신의 user_id로만 삽입
CREATE POLICY "typing_sessions_insert_authenticated"
ON typing_sessions
FOR INSERT
TO authenticated
WITH CHECK (user_id = auth.uid());

-- UPDATE: 자신의 레코드만 수정
CREATE POLICY "typing_sessions_update_authenticated"
ON typing_sessions
FOR UPDATE
TO authenticated
USING (user_id = auth.uid())
WITH CHECK (user_id = auth.uid());

-- DELETE: 자신의 레코드만 삭제
CREATE POLICY "typing_sessions_delete_authenticated"
ON typing_sessions
FOR DELETE
TO authenticated
USING (user_id = auth.uid());

자주 쓰는 패턴 ​

공개 읽기 + 본인만 쓰기 ​

sql
-- 누구나 읽기 가능 (블로그 포스트 등)
CREATE POLICY "posts_select_public"
ON posts FOR SELECT TO anon, authenticated
USING (true);

-- 작성자만 수정/삭제
CREATE POLICY "posts_update_owner"
ON posts FOR UPDATE TO authenticated
USING (author_id = auth.uid())
WITH CHECK (author_id = auth.uid());

팀 기반 접근 (조인 테이블 활용) ​

sql
-- team_members 테이블에 있는 유저만 접근
CREATE POLICY "documents_select_team_member"
ON documents FOR SELECT TO authenticated
USING (
  EXISTS (
    SELECT 1 FROM team_members
    WHERE team_members.team_id = documents.team_id
    AND team_members.user_id = auth.uid()
  )
);

Service Role은 RLS 우회 ​

Edge Function에서 SUPABASE_SERVICE_ROLE_KEY를 사용하면 RLS를 우회합니다. 관리자 작업이나 백그라운드 작업에 활용합니다.

typescript
// Edge Function에서 서비스 롤 사용 (RLS 우회)
const supabase = createClient(
  Deno.env.get('SUPABASE_URL')!,
  Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!  // ← RLS 우회
)

⚠️ SERVICE_ROLE_KEY는 클라이언트(브라우저)에 절대 노출하면 안 됩니다. Edge Function 서버 사이드에서만 사용하세요.


RLS 정책 테스트 프롬프트 ​

작성 후 Claude에게 테스트 쿼리도 요청하세요.

"방금 작성한 RLS 정책이 잘 작동하는지 확인하는 SQL 테스트 케이스를 작성해줘.
- 인증된 유저가 자신의 레코드를 조회/수정/삭제하는 케이스
- 다른 유저의 레코드에 접근했을 때 거부되는 케이스
- 비인증 유저(anon)가 접근했을 때 거부되는 케이스"

CLAUDE.md에 공통 규칙 등록하기 ​

markdown
## Supabase RLS 규칙
- RLS 정책은 작업별(SELECT/INSERT/UPDATE/DELETE)로 분리해서 작성
- 정책 이름: "[테이블명]_[작업]_[역할]" 형식 (예: users_select_authenticated)
- 인증 함수: auth.uid() 사용 (current_user 사용 금지)
- 새 테이블 생성 시 항상 RLS 활성화 포함
- Service Role Key는 Edge Function에서만 사용, 클라이언트 노출 금지

PR 환영합니다 ✦