Supabase RLS 정책 예제 가이드: 조회, 생성, 수정, 삭제까지
Dev
마지막 업데이트

Supabase RLS 정책 예제 가이드: 조회, 생성, 수정, 삭제까지


RLS는 Supabase가 쉽다고 느껴지다가 갑자기 “왜 아무 데이터도 안 나오지?”로 바뀌는 지점입니다. 데이터가 사라진 것이 아니라, 요청이 정책을 통과하지 못하고 있을 가능성이 큽니다.

이 글은 초보자가 먼저 이해해야 할 흐름부터 설명합니다. RLS를 켠 뒤 무엇이 달라지는지 짚고, 조회·생성·수정·삭제 정책 예제를 가장 단순한 형태부터 따라갈 수 있게 정리했습니다.


RLS는 무엇을 하는가

Supabase는 PostgreSQL Row Level Security를 이용해 어떤 row에 접근할 수 있는지 제어합니다.

가장 단순한 이해는 이렇습니다. 정책은 쿼리에 붙는 추가 필터처럼 동작합니다.

그래서 대시보드에서는 멀쩡해 보여도 앱에서는 아무것도 안 보일 수 있습니다.


RLS 활성화 방법

Supabase 공식 문서에 나온 기본 활성화 명령은 아래와 같습니다.

alter table profiles enable row level security;

여기서 중요한 점이 하나 있습니다. Supabase 문서에서는 RLS를 켠 뒤에는 public anon key를 통한 브라우저 접근이 정책 없이는 row를 노출하지 않는다고 설명합니다.

많은 초보자가 여기서 “앱이 고장났다”고 느끼지만, 사실은 정책이 아직 없는 상태인 경우가 많습니다.


auth.uid()가 중요한 이유

Supabase는 현재 로그인한 사용자를 기준으로 정책을 만들 수 있게 auth.uid() 같은 helper를 제공합니다.

가장 흔한 패턴은 이렇습니다.

using ((select auth.uid()) = user_id)

공식 문서에서도 설명하듯, 인증되지 않은 요청에서는 auth.uid()null이 되므로, 로그인 여부를 명확히 고려한 정책이 더 안전합니다.


예시 테이블

간단한 예시로 아래 같은 테이블을 생각해보겠습니다.

create table profiles (
  id uuid primary key,
  user_id uuid references auth.users,
  display_name text
);

그리고 RLS를 켭니다.

alter table profiles enable row level security;

예제 1: 자기 row만 조회 허용

create policy "Users can read their own profile"
on profiles
for select
to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id);

가장 이해하기 쉬운 첫 패턴이라서 RLS 입문에 좋습니다.


예제 2: 자기 row만 생성 허용

create policy "Users can insert their own profile"
on profiles
for insert
to authenticated
with check ((select auth.uid()) is not null and (select auth.uid()) = user_id);

생성에서는 with check가 핵심입니다. 새로 들어오는 row가 조건을 만족하는지 검사하기 때문입니다.


예제 3: 자기 row만 수정 허용

create policy "Users can update their own profile"
on profiles
for update
to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id)
with check ((select auth.uid()) is not null and (select auth.uid()) = user_id);

Supabase 문서에서도 보듯, update는 select와 함께 생각해야 할 때가 많습니다. 읽기 조건이 빠져 있으면 예상과 다르게 동작할 수 있습니다.


예제 4: 자기 row만 삭제 허용

create policy "Users can delete their own profile"
on profiles
for delete
to authenticated
using ((select auth.uid()) is not null and (select auth.uid()) = user_id);

같은 소유권 기준을 삭제에도 적용한 형태입니다.


하면 안 되는 것

1. RLS만 켜고 정책 없이 앱이 고장났다고 생각하기

사실 앱이 망가진 게 아니라 정책이 아직 없는 상태일 수 있습니다.

2. service role key를 브라우저에 쓰기

Supabase 문서에서도 service key는 RLS를 우회할 수 있으므로 브라우저에 노출하면 안 된다고 설명합니다.

3. to authenticated를 빼먹기

역할 범위를 명확히 제한하는 것이 더 안전합니다.

4. auth.uid() = user_id만 쓰고 비로그인 요청을 생각하지 않기

로그인하지 않은 경우에는 기대한 방식으로 통과하지 않습니다.


초보자에게 무난한 진행 순서

RLS를 처음 다룬다면 보통 이 순서가 좋습니다.

  1. 테이블 생성
  2. RLS 활성화
  3. select 정책 추가
  4. insert / update 정책 추가
  5. 실제 로그인 세션으로 테스트
  6. 브라우저 코드가 service role key를 쓰지 않는지 확인

한 번에 모든 정책을 쓰는 것보다 훨씬 디버깅이 쉽기 때문에, 복잡한 권한 규칙으로 가기 전에 이 순서부터 먼저 검증하는 편이 좋습니다.


FAQ

Q. RLS를 켠 뒤 왜 아무 row도 안 나올까?

정책이 없으면 public key 기준 브라우저 접근은 기본적으로 막히기 때문입니다.

Q. usingwith check의 차이는 무엇일까?

using은 기존 row에 대한 접근 조건이고, with check는 새로 생성되거나 변경되는 row가 허용되는지 검사합니다.

Q. service role key를 쓰면 빨리 해결되지 않을까?

브라우저에서는 쓰면 안 됩니다. 안전하지 않고 RLS의 목적 자체를 무너뜨립니다.


다음에 읽기 좋은 글

  • 정책을 더 쓰기 전에 Auth와 데이터베이스 기본 흐름까지 같이 잡고 싶다면 Supabase 입문 가이드로 이어서 보면 좋습니다.
  • 정책이 실제 앱 안에서 어떻게 이어지는지 보고 싶다면 Supabase RAG 챗봇 가이드가 다음 글입니다.

관련 글

Sources:

먼저 읽어볼 가이드

검색 유입이 많은 핵심 글부터 이어서 보세요.