반응형

 

Supabase를 사용하다 보면 Row Level Security(RLS)를 켜놓은 상태에서 UPDATE 쿼리를 실행했는데도 다음과 같은 에러를 만날 수 있습니다:

 

ERROR:  permission denied for table user_profiles

 

“분명히 Policy도 설정했는데 왜 안 되지?” 싶은 분들을 위해, 이 글에서는 이 에러의 근본 원인과 해결 방법을 예제로 함께 정리합니다.

 

문제 상황

 

시도한 쿼리

update public.user_profiles
set name = '테스트'
where id = 'e0804eda-xxxxx-xxxxx-xxxxxx-xxxx';

 

 

발생한 에러 로그

ERROR:  permission denied for table user_profiles

 

이 에러는 단순히 RLS 정책이 없어서가 아닙니다. 오히려 RLS는 잘 설정되어 있었지만, PostgreSQL 권한(GRANT) 자체가 누락되어 있어서 발생하는 것입니다.

 


원인 분석: Supabase의 보안 구조

 

Supabase는 다음 두 단계를 거쳐 요청을 허용합니다:

 

1. PostgreSQL 권한 (GRANT)

역할(Role)이 테이블에 접근할 수 있는지

예: SELECT, INSERT, UPDATE, DELETE 권한을 가졌는가?

 

2. Row Level Security (RLS) 정책

해당 row에 접근 가능한 조건을 만족하는가?

예: 로그인한 사용자의 auth.uid()와 row의 id가 일치하는가?

 

즉, GRANT + RLS 둘 다 통과해야 UPDATE가 작동합니다.

 

 

 


 해결 방법

 

테이블 권한 부여

GRANT UPDATE ON public.user_profiles TO authenticated;

 

필요에 따라 SELECT도 함께 부여합니다:

GRANT SELECT ON public.user_profiles TO authenticated;

 

 

 


RLS 활성화

ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;

 

RLS 정책 작성

 

로그인한 사용자가 자신의 프로필만 수정 가능하게 만들기:

CREATE POLICY "Users can update their own profile"
ON public.user_profiles
FOR UPDATE
TO authenticated
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);

 

USING: 해당 row를 조회할 수 있는 조건

WITH CHECK: 해당 row를 수정할 수 있는 조건

 

 


최종 정리

 

설정항목 설명 예시
GRANT 테이블에 대한 접근 권한 부여 GRANT UPDATE ON ... TO authenticated
RLS ENABLE Row-Level Security 사용 설정 ALTER TABLE ... ENABLE ROW LEVEL SECURITY
POLICY 행(row) 단위 조건 설정 USING, WITH CHECK 조건 작성

 

 


결과: 정상 동작

 

위 설정이 완료되면, Supabase Auth를 통해 로그인한 사용자가 자신의 id를 가진 row에 대해 다음과 같은 쿼리를 정상적으로 실행할 수 있습니다.

update public.user_profiles
set name = '테스트'
where id = '로그인한_사용자의_id';

 

 

 

마무리

 

처음엔 "RLS 정책만 설정하면 된다"고 생각하기 쉽지만, Supabase는 PostgreSQL 위에서 작동하는 만큼 기본 권한 설정도 꼭 필요합니다. 앞으로는 permission denied 에러가 나면 다음 두 가지를 먼저 체크해보세요:

1. GRANT 권한이 있는가?

2. RLS Policy가 올바른가?

 

이 구조만 이해하면, Supabase의 보안 모델을 더 자신 있게 다룰 수 있을 거예요.

 

 

# 해피코딩!

반응형

 

RLS란?

Supabase 본격적으로 사용하다보면, RLS를 접하게 되는데요. 이 RLS 개념이 상당히 편리한 기능입니다. 

Row Level Security , 즉 DB 의 특정 필드의 값을 채크하여 query 시 Row 단위로 제외 시킬 수 있는 방식입니다.

 

id user_id created_at title content team_id
1 mika 11 안녕하세요. 개발 진행 합니다. 1
2 other 12 개발 의뢰 합니다 이런거 개발해 주세요. 2

말로 표현하려니 좀 어려웠는데요.

 

위와 같은 table이 있다고 한다면, user_id 가 mika. 인 데이터만 가져오려고 한다면,

query시 user_id 가 mika 인 조건을 추가해서 client에서 호출하면 됩니다.

그러나, 만약 클라이언트 개발자가 이 과정에서 특정 경우에 조건을 추가하지 않았다면, 다른 사람의 정보들까지 노출되버리겠죠.

이를 시스템 상으로 보완할수 있는 하나의 방법이 RLS 입니다.

RLS에 기본적인 예제로  acceess token에서 auth 정보를 가져와서 접근 권한을 설정할 수 있기 때문에, 로그인한 사용자에게 맞춰서 데이터를 필터링 할 수 있게 됩니다.

 

 

문제는 뷰 (View)

View 는 기본적으로 RLS가 적용 안됩니다.

VIEW로 쿼리할때 데이터가 필터링 안되는지 몇시간 고민하다 찾게 된 사항입니다.

 

만약 View를 만들어서 사용하려면, View를 만들때 RLS가 적용되도록 해야합니다.

WITH ( security_invoker = TRUE )

 

view 생성시 이렇게 security_invoker를 설정해주면 RLS가 적용됩니다.

 

create or replace view
[view이름] WITH ( security_invoker = TRUE ) as
select * form [talbe이름]

 

 

아래와 같은 형식이 되겠죠..

 
create or replace view
public.myview WITH ( security_invoker = TRUE ) as
select
mydata.*
user_profiles.name as user_name,
user_profiles.email as user_email,
org.name as org_name,
sites.name as site_name

from
mydata
left join user_profiles on mydata.user_id = user_profiles.id
left join org on mydata.org_id = org.id
...

 

 

 


ALTER를 이용하여 기존의 View에 적용하는 방법.

View는 RLS 를 설정 할 수 없습니다.  이걸로 좌절하고 주말을 보냈는데, 찾다보니 방법이 있더군요.

ALTER VIEW my_view SET (security_invoker = on);

 

이렇게 설정을 하면 my_view 에서 query하는 table에 RLS가 적용 됩니다.

다만 이렇게 했을때 문제는 my_view 가 접근하는 모든 table에 RLS가 잘 적용 되어있어야 동작 한다는 것입니다.

 

a table은 접근 권한이 있고 b table은 접근 권한이 없으면 퍼미션 에러가 발생합니다.

View 뿐만 아니라, Table 에서도 마찬가지 입니다.

(이건 시스템을 어떻게 설계하느냐의 문제이기 때문에 이런 부분 고려해서 설계하면 좋겠네요)

 

 

RLS를 활용하여 내 서비스의 기능을 발전 시켜 나가보세요~

 

 

#해피코딩!!

반응형

 

 

using (
  created_at > (current_timestamp - interval '1 day')
);

+ Recent posts