목차
지난 편에서는 MyLog의 API Key와 환경설정을 정리했습니다.
특히 하나를 확실하게 구분했습니다.
Publishable Key
→ 클라이언트 사용 가능
Secret Key
→ 서버 전용그리고 진짜 Secret은 .env에 숨기는 것이 아니라 애초에 Flutter 앱으로 내려보내지 않는 구조가 중요하다는 것도 살펴봤습니다.
그렇다면 이제 꽤 안전해진 것 같습니다.
회원가입도 있고,
로그인도 있고,
Supabase RLS도 있고,
Secret Key도 앱에 없습니다.
이제 보안은 끝난 걸까요?
아직 한 가지 중요한 문제가 남았습니다.
로그인한 사용자가
무엇까지 할 수 있는가
입니다.
오늘은 #12에서 잠깐 등장했던 두 단어를 제대로 구분해보겠습니다.
Authentication
그리고
Authorization
입니다.
1. 로그인은 출입증을 받은 것뿐이다
먼저 Authentication부터 다시 보겠습니다.
Authentication은 인증입니다.
질문은 하나입니다.
너 누구야?
사용자가 MyLog에 이메일과 비밀번호를 입력합니다.
Supabase Auth가 확인합니다.
이메일
+
비밀번호
↓
인증 성공
↓
User 확인여기까지가 Authentication입니다.
Supabase 공식 문서에서도 Authentication을 사용자의 신원을 확인하는 과정, Authorization을 사용자가 어떤 리소스에 접근할 수 있는지 확인하는 과정으로 구분합니다.
로그인에 성공했다는 것은
신원 확인이 끝났다
는 뜻입니다.
모든 권한을 받았다는 뜻은 아닙니다.
2. Authorization은 "이거 해도 돼?"를 판단한다
이번에는 Authorization, 인가 또는 권한 확인입니다.
로그인한 사용자가 기록 하나를 조회하려고 합니다.
여기서 질문이 달라집니다.
이 사용자가 이 Record를 볼 수 있는가?
예를 들어 사용자 A와 사용자 B가 있다고 해보겠습니다.
User A
Record A1
Record A2
User B
Record B1
Record B2사용자 A는
Record A1 → 조회 가능
Record A2 → 조회 가능
Record B1 → 조회 불가
Record B2 → 조회 불가여야 합니다.
사용자가 누구인지 알아내는 것이 Authentication이고,
그 사용자가 어느 Record까지 접근할 수 있는지 결정하는 것이 Authorization입니다.
정리하면 아주 간단합니다.
Authentication
너 누구야?
Authorization
그래서 어디까지 해도 돼?3. 로그인했다고 모든 데이터가 안전한 것은 아니다
여기서 바이브 코딩에서 자주 나오는 착각이 하나 있습니다.
AI에게 이렇게 요청했다고 해보겠습니다.
로그인한 사용자만 기록 화면에 들어갈 수 있게 해줘.
AI가 AuthGate를 잘 만들어줬습니다.
로그인하지 않으면 기록 화면에 들어갈 수 없습니다.
그러면 안전할까요?
아직 아닙니다.
화면 접근과 데이터 접근은 별개입니다.
예를 들어 Flutter에서는 자기 기록만 요청하도록 만들었지만
DB가 모든 Record 접근을 허용하고 있다면 누군가 앱 대신 API를 직접 호출할 수도 있습니다.
정상 앱
User A
↓
A의 Record만 요청하지만 공격자는
직접 API 요청
↓
B의 Record 요청을 시도할 수 있습니다.
그래서 권한은 UI가 아니라 데이터가 있는 곳에서도 강제해야 합니다.
4. 버튼을 숨기는 것은 보안이 아니다
관리자 기능을 하나 만든다고 생각해보겠습니다.
일반 사용자에게는
[내 기록]
[로그아웃]만 보여줍니다.
관리자에게는
[내 기록]
[전체 사용자 관리]
[전체 기록 관리]
[로그아웃]를 보여줍니다.
Flutter에서 이렇게 할 수 있습니다.
if admin
→ 관리자 버튼 표시그렇다면 일반 사용자는 관리자 기능을 사용할 수 없을까요?
버튼만 숨겼다면 아닙니다.
UI에서 버튼이 안 보이는 것은 사용자 경험을 위한 처리입니다.
진짜 보안은
일반 사용자가 관리자 API를 직접 호출해도
↓
거절되어야 합니다.
이 원칙은 꼭 기억해두겠습니다.
화면에서 숨기는 것은 UX이고, 서버에서 막는 것이 권한 제어다.
5. 우리가 만든 RLS가 바로 Authorization이었다
#13에서 records 테이블에 이런 RLS Policy를 만들었습니다.
create policy "Users can read own records"
on public.records
for select
to authenticated
using (
(select auth.uid()) = user_id
);당시에는
자기 데이터만 보는 정책
정도로 이해했습니다.
지금 다시 보면 이것이 바로 Authorization입니다.
현재 로그인 사용자
auth.uid()
↓
Record의 user_id와 비교
↓
같음
→ 접근 허용
다름
→ 접근 거절Supabase의 RLS는 이런 권한 규칙을 데이터베이스 내부에서 실행합니다. 공개 스키마의 테이블에서는 RLS를 활성화하고 Supabase Auth와 결합해 사용자별 데이터 접근을 제한하도록 공식 문서에서도 안내하고 있습니다.
6. PostgreSQL도 기본적으로 "허용된 행만" 보여준다
RLS는 Supabase만의 특별한 기능은 아닙니다.
Supabase가 사용하는 PostgreSQL 자체 기능입니다.
PostgreSQL의 Row Security Policy는 조회뿐 아니라 INSERT, UPDATE, DELETE에서도 사용자나 역할에 따라 접근할 수 있는 행을 제한할 수 있습니다. RLS가 활성화된 상태에서 적용 가능한 정책이 없다면 기본적으로 해당 행에 접근하지 못하는 Default Deny 방식이 적용됩니다.
보안에서는 꽤 마음에 드는 기본값입니다.
모르겠으면 허용이 아니라
명시적으로 허용하지 않았다면 거절에 가깝기 때문입니다.
7. SELECT만 막았다고 끝나는 것도 아니다
현재 MyLog에서 기록 하나를 생각해보겠습니다.
사용자가 할 수 있는 작업은 앞으로 네 가지가 될 수 있습니다.
SELECT
조회
INSERT
생성
UPDATE
수정
DELETE
삭제각 작업마다 권한을 생각해야 합니다.
예를 들어
A의 기록 조회
→ A 가능
A의 기록 수정
→ A 가능
A의 기록 삭제
→ A 가능
B가 A 기록 수정
→ 불가입니다.
PostgreSQL의 CREATE POLICY는 SELECT, INSERT, UPDATE, DELETE별로 정책을 만들 수 있으며 기존 행에 대한 조건은 USING, 새롭게 만들어지거나 변경되는 행에는 WITH CHECK 조건을 사용할 수 있습니다.
그래서 권한을 설계할 때 단순히
이 테이블 사용할 수 있음
이 아니라
누가, 어떤 작업을, 어떤 데이터에 할 수 있는가
까지 봐야 합니다.
8. 권한을 문장으로 먼저 적어보자
SQL부터 만들 필요는 없습니다.
먼저 사람이 이해할 수 있는 문장으로 작성합니다.
현재 MyLog의 기본 사용자는 이렇게 정의할 수 있습니다.
일반 사용자
- 자신의 기록을 조회할 수 있다.
- 자신의 userId로 기록을 만들 수 있다.
- 다른 사용자의 기록을 조회할 수 없다.
- 다른 사용자의 기록을 수정할 수 없다.
- 다른 사용자의 기록을 삭제할 수 없다.아직 수정과 삭제 기능은 만들지 않았지만,
보안 모델은 미리 이렇게 이해할 수 있습니다.
이걸 표 대신 더 간단하게 표현하면
User
자기 Record
→ 허용
다른 사람 Record
→ 거절입니다.
현재 MyLog에서는 이것만으로도 상당히 충분합니다.
9. 모든 앱에 관리자 Role이 필요한 것은 아니다
권한 이야기를 하면 AI가 자주 이런 구조를 제안합니다.
guest
user
manager
admin
super_admin멋있습니다.
하지만 MyLog에는 현재 관리자 화면조차 없습니다.
그런데 Role 다섯 개부터 만드는 것이 필요할까요?
아닙니다.
우리는 지금까지 계속 같은 원칙을 사용했습니다.
필요해졌을 때 추가한다.
현재 MyLog에는
authenticated user와 자기 데이터 접근 정책이면 충분합니다.
관리 기능이 실제로 필요해질 때 Role을 추가합니다.
권한도 기능과 마찬가지로 미리 살찌우지 않겠습니다.
10. 그래도 Role과 Permission의 차이는 알아두자
조금 뒤 프로젝트가 커지면 Role이 등장할 수 있습니다.
예를 들어
user
admin두 역할이 있다고 해보겠습니다.
Role은 사용자의 역할입니다.
Permission은 실제 행동 권한입니다.
예를 들어
Role
admin이 가지고 있는 Permission은
records.read.any
records.delete.any
users.read
users.disable같이 표현할 수 있습니다.
즉
User
↓
Role
↓
Permission구조입니다.
이런 방식을 RBAC, Role-Based Access Control이라고 부릅니다.
11. Supabase에서도 RBAC를 만들 수 있다
Supabase는 Custom Claims를 이용해 JWT에 사용자 역할 같은 정보를 넣고, 이를 RLS Policy와 연결하는 RBAC 방식을 공식적으로 안내하고 있습니다.
예를 들어 JWT에
user_role = admin같은 Claim을 포함하고,
RLS에서
이 사용자에게 records.delete 권한이 있는가?를 판단하도록 구성할 수 있습니다. 현재 Supabase 공식 RBAC 가이드에서는 Custom Access Token Auth Hook을 이용해 JWT에 Role을 추가하고 이를 RLS 정책에서 활용하는 방법을 설명하고 있습니다.
하지만 오늘 당장 MyLog에 구현하지는 않습니다.
지금은 구조만 이해하면 됩니다.
12. Role을 아무 사용자 정보에 넣으면 안 된다
여기서 중요한 함정이 있습니다.
Supabase 사용자 정보에는 여러 Metadata 영역이 있습니다.
예를 들어 사용자 스스로 수정할 수 있는 raw_user_meta_data에
role = admin을 저장하고,
RLS가 이 값을 믿는다고 해보겠습니다.
위험합니다.
Supabase 공식 RLS 문서에서는 raw_user_meta_data는 인증된 사용자가 수정할 수 있기 때문에 Authorization 데이터 저장 장소로 적합하지 않다고 설명합니다.
반대로 사용자가 직접 수정할 수 없는 raw_app_meta_data는 권한 정보에 활용할 수 있습니다.
즉 이런 구조는 피합니다.
사용자가 수정 가능한 Profile
↓
role = admin
↓
관리자 권한 획득이런 일이 생기면 게임에서 닉네임을
GM
으로 바꿨더니 진짜 운영자 권한이 생기는 수준입니다. 🎮
13. 권한 정보는 사용자가 마음대로 바꿀 수 없어야 한다
관리자 Role이 필요한 경우 기본 원칙은 간단합니다.
사용자가 직접 수정 가능한 데이터
≠
권한의 기준권한 정보는
서버,
관리자 시스템,
보호된 DB 영역,
Custom Claims
등 신뢰할 수 있는 경로에서만 변경되어야 합니다.
그리고 Flutter 앱은
나는 관리자입니다.
라고 주장하는 역할이 아니라,
서버와 DB가 검증한 정보를 기반으로 동작해야 합니다.
14. RLS와 Role은 경쟁 관계가 아니다
이것도 헷갈리기 쉽습니다.
RLS를 쓸지 Role을 쓸지 고르는 문제가 아닙니다.
둘을 같이 사용할 수 있습니다.
예를 들어
일반 User
↓
자기 user_id Record만 접근은 소유권 기반 RLS입니다.
관리자는
Admin Role
↓
권한 정책 확인
↓
모든 Record 조회 가능처럼 Role 기반 권한을 추가할 수 있습니다.
즉 정책이 이렇게 발전할 수 있습니다.
본인 데이터인가?
또는
관리 권한이 있는가?입니다.
다만 정책이 많아질수록 보안 설계도 복잡해지므로 필요 없는 권한은 만들지 않는 것이 좋습니다.
15. GRANT와 RLS도 역할이 다르다
#13에서 잠깐 등장했지만 여기서 한번 더 정리하겠습니다.
Supabase에서 데이터 접근을 생각할 때는 크게 두 층을 볼 수 있습니다.
Table Grant
이 역할이 SELECT 자체를 할 수 있는가?
RLS
SELECT 가능한 행 중
어떤 Row에 접근할 수 있는가?즉 두 개의 문이 있는 셈입니다.
첫 번째 문
SELECT 권한 있음?
두 번째 문
이 Record를 볼 수 있음?현재 Supabase 문서 역시 Postgres Grant와 RLS Policy를 별개의 권한 계층으로 설명하며 필요한 최소 권한만 역할에 부여한 뒤 RLS로 행 접근을 제한하도록 안내합니다.
권한을 많이 열어두고 UI에서 가리는 것보다,
처음부터 필요한 만큼만 여는 편이 낫습니다.
16. 최소 권한 원칙을 기억하자
보안에서 자주 나오는 중요한 원칙이 있습니다.
Principle of Least Privilege
최소 권한 원칙입니다.
말 그대로
필요한 일을 수행할 수 있는 최소한의 권한만 준다.
는 뜻입니다.
현재 MyLog 사용자가 필요한 것은
자기 Record 조회
자기 Record 생성입니다.
그런데 편하다고
모든 사용자 Record 조회
모든 Record 수정
모든 Record 삭제
사용자 관리까지 열어줄 이유는 없습니다.
권한은
혹시 필요할지 모르니까 미리 주는 것
이 아니라
필요할 때 추가하는 것
이 좋습니다.
17. 바이브 코딩에서는 관리자 기능을 특히 조심한다
AI에게 이런 요청을 했다고 해보겠습니다.
관리자 화면에서 모든 사용자의 기록을 볼 수 있게 만들어줘.
AI가 Flutter에서 다음처럼 구현합니다.
if user.email == "admin@example.com"
→ 전체 Record 조회겉보기에는 작동합니다.
하지만 이것만 믿으면 안 됩니다.
앱 코드는 사용자 기기에 있습니다.
클라이언트의 조건문은 진짜 권한 경계가 될 수 없습니다.
관리 기능이라면
Flutter
↓
인증된 요청
↓
서버 또는 DB에서 권한 검증
↓
허용된 경우에만 관리자 작업처럼 실제 데이터 접근 지점에서 권한을 다시 확인해야 합니다.
18. 이메일 주소로 관리자 여부를 결정해도 될까?
작은 테스트에서는
admin@example.com같은 특정 이메일을 비교하고 싶어질 수 있습니다.
하지만 실제 권한 모델로는 좋지 않습니다.
이메일은 사용자의 식별 정보이지 권한 모델 자체가 아닙니다.
관리자가 여러 명으로 늘어나면 코드에 이메일을 계속 추가해야 하고,
관리 권한 회수나 변경도 불편합니다.
제품 단계에서는
Role
Permission을 별도로 관리하는 것이 훨씬 명확합니다.
19. AI에게 권한 구조부터 분석하게 해보자
이번 편에서는 기능을 크게 추가하지 않습니다.
대신 현재 MyLog의 권한 상태를 점검해보겠습니다.
AI에게 이렇게 요청할 수 있습니다.
MyLog 프로젝트를 Authorization 관점에서 분석해줘.
아직 코드를 수정하지 마.
현재 기본 정책은
로그인한 사용자가 자신의 Record만 사용하는 것이다.
다음 항목을 확인해줘.
1. Authentication과 Authorization이 어디에서 처리되는지
2. UI에서만 제한하고 있는 기능이 있는지
3. Record 접근을 RLS가 실제로 보호하고 있는지
4. 사용자 A가 사용자 B의 Record에 접근할 가능성
5. SELECT와 INSERT 권한 정책
6. 현재 앱에 불필요하게 넓은 권한
7. using(true), 전체 허용 Policy 존재 여부
8. Flutter에서 관리자 권한을 임의로 판단하는 코드
9. 사용자 수정 가능한 Metadata를 권한 판단에 사용하는지
10. Secret Key 또는 service_role을 이용해 RLS를 우회하는 부분
발견한 문제를
Critical / High / Medium / Low로 구분해줘.
아직 수정은 하지 마.지금까지 만든 코드를 새로운 관점으로 다시 바라보는 것입니다.
20. 가장 중요한 테스트는 여전히 사용자 두 명이다
#13에서도 했던 테스트입니다.
하지만 권한 검증에서 굉장히 중요하기 때문에 다시 합니다.
계정을 두 개 준비합니다.
User A
User BA로 로그인합니다.
A 기록을 만듭니다.
B로 로그인합니다.
A 기록이 보이지 않아야 합니다.
여기까지는 이미 했습니다.
이번에는 조금 더 공격적으로 확인합니다.
가능하다면 개발 도구나 테스트 코드를 이용해
A의 Record ID를 알고 있는 B가 그 Record를 직접 요청하는 상황
도 확인합니다.
User B
↓
Record A의 ID를 직접 요청
↓
RLS
↓
접근 거부ID를 모르면 안전한 것이 아닙니다.
ID를 알아도 접근할 수 없어야 안전한 것입니다.
21. URL이나 Record ID를 숨기는 것은 보안이 아니다
웹 서비스에서 자주 나오는 실수와 같습니다.
예를 들어
/records/12345라는 주소가 있습니다.
다른 사용자의 Record 번호를 모르면 볼 수 없습니다.
그렇다고 안전한 것은 아닙니다.
누군가
12346
12347
12348을 시도할 수도 있기 때문입니다.
모바일 앱에서도 같습니다.
Record ID는 식별자일 뿐,
접근 권한을 의미하지 않습니다.
Record ID 알고 있음
≠
Record 볼 권한 있음이 구분을 기억하겠습니다.
22. 로그아웃 후 데이터가 남는지도 다시 확인한다
권한 문제는 DB에서만 발생하지 않습니다.
사용자 A로 로그인했습니다.
A의 기록이 화면에 표시됩니다.
로그아웃합니다.
사용자 B로 로그인합니다.
그런데 API 호출 전 잠깐
A의 기록이 화면에 보인다면?
RLS는 정상일 수도 있습니다.
하지만 앱의 메모리에 이전 사용자 데이터가 남아 있는 것입니다.
따라서 로그아웃할 때
Session 제거
+
사용자 전용 화면 상태 초기화
+
캐시된 Record 제거도 확인해야 합니다.
Authorization은 서버에서만 보는 문제가 아니라 클라이언트 상태 관리에서도 개인정보 노출을 막아야 하는 문제입니다.
23. 앞으로 로컬 캐시가 생기면 더 중요해진다
현재는 클라우드 중심으로 단순하게 운영하고 있습니다.
나중에 SQLite를 다시 활용해 오프라인 캐시를 만들면 문제가 하나 더 생깁니다.
User A 로그인
↓
A Record 로컬 캐시
로그아웃
↓
User B 로그인이때 로컬 DB의 A 데이터가 B에게 보이지 않도록 해야 합니다.
따라서 사용자별 로컬 저장을 만들 때도
userId로 데이터를 구분하거나 로그아웃 시 민감한 캐시를 처리하는 설계가 필요합니다.
오늘 당장 만들지는 않지만 이런 문제가 있다는 것 정도는 기억해두겠습니다.
24. RLS를 테스트할 때 관리자 화면만 믿지 않는다
Supabase Dashboard에서 데이터가 잘 보입니다.
그래서
RLS가 안 먹는 것 같은데?
라고 생각할 수도 있습니다.
PostgreSQL의 RLS에는 테이블 소유자나 BYPASSRLS 권한을 가진 역할처럼 일반 사용자와 다른 적용을 받는 경우가 있습니다. PostgreSQL 문서에서도 Superuser와 BYPASSRLS Role은 Row Security를 우회하며 테이블 소유자 역시 일반적으로 RLS 적용을 받지 않는다고 설명합니다.
따라서 실제 보안 검증은
Flutter에서 로그인한 일반 사용자 Session
기준으로 해야 합니다.
관리자 도구에서 보이는 결과만 가지고 사용자 권한을 판단하면 안 됩니다.
25. 여러 Policy를 만들 때도 주의한다
RLS 정책이 한두 개일 때는 이해하기 쉽습니다.
그런데 나중에 정책이 늘어나면
Own Records Policy
Admin Policy
Team Member Policy
Shared Record Policy같은 것들이 동시에 적용될 수 있습니다.
PostgreSQL에서는 여러 Policy가 적용될 때 기본적인 permissive 정책들은 OR 방식으로 결합될 수 있으며 restrictive 정책은 AND 방식으로 적용될 수 있습니다.
그래서 권한 정책이 많아지면
정책 하나하나는 안전해 보이는데 합치면 예상보다 넓게 열린다.
는 일이 생길 수 있습니다.
이 단계부터는 RLS 역시 코드처럼 관리하고 테스트해야 합니다.
26. 권한 정책도 Git으로 관리하자
Supabase Dashboard에서 SQL을 한 번 실행하고 끝내면 나중에 어떤 정책을 만들었는지 추적하기 어렵습니다.
프로젝트가 커지면 DB Schema와 RLS Policy도 Migration이나 SQL 파일 형태로 프로젝트에서 관리하는 것이 좋습니다.
예를 들어
supabase/
└── migrations/또는 프로젝트에 맞는 DB 문서에서
records table
SELECT Policy
INSERT Policy를 관리합니다.
핵심은
현재 운영 DB가 어떤 권한 규칙을 가지고 있는지 코드와 문서로 추적 가능해야 한다
는 것입니다.
AI에게 정책을 바꾸게 했다면 변경 이유도 Git에 남깁니다.
27. PROJECT_RULES에도 Authorization 원칙을 추가한다
이번 편의 내용을 프로젝트 규칙으로 승격해보겠습니다.
## Authorization Rules
- 로그인 성공을 모든 기능의 사용 권한으로 간주하지 않는다.
- UI에서 버튼을 숨기는 것을 보안 정책으로 사용하지 않는다.
- 사용자 데이터 접근은 서버 또는 DB에서도 검증한다.
- Record는 auth.uid()와 user_id를 기준으로 소유권을 검증한다.
- 다른 사용자의 Record 접근을 허용하지 않는다.
- RLS 오류를 해결하기 위해 전체 허용 Policy를 만들지 않는다.
- 사용자 수정 가능한 Metadata를 관리자 Role 판단에 사용하지 않는다.
- 관리 권한은 신뢰할 수 있는 서버측 데이터나 Custom Claims를 사용한다.
- 권한은 필요한 만큼만 부여한다.
- 권한 변경 후 반드시 서로 다른 사용자 계정으로 테스트한다.이제 AI가 앞으로 관리자 기능을 추가할 때도 기준이 생깁니다.
28. 오늘의 삽질 방지 팁 🛠️
AI에게 관리자 기능을 요청했는데 이런 코드가 나왔다면 한번 의심해보세요.
관리자 버튼이 일반 사용자에게 안 보인다.
따라서 안전하다.아닙니다.
다음 질문을 해야 합니다.
일반 사용자가 UI를 거치지 않고
해당 API나 DB 작업을 직접 요청하면
서버 또는 DB에서도 거부되는가?YES라면 권한 제어가 존재합니다.
NO라면 버튼만 숨긴 것입니다.
보안에서는 사용자가 우리가 만든 화면만 착하게 사용한다고 가정하지 않습니다.
29. AI에게 일부러 공격자 역할도 시켜보자
이번에는 AI를 조금 다른 방식으로 써볼 수 있습니다.
MyLog의 현재 Authorization 구조를
악의적인 일반 사용자의 관점에서 검토해줘.
코드를 수정하지 말고 공격 가능성만 분석해줘.
가정:
공격자는 정상 계정 하나를 가지고 있다.
자신의 Access Token도 사용할 수 있다.
Flutter UI를 반드시 거칠 필요는 없다.
다음을 시도한다고 가정해줘.
1. 다른 사용자의 Record ID 직접 조회
2. 다른 userId로 Record 생성
3. 관리자 화면 API 직접 호출
4. Flutter 조건문 우회
5. user metadata를 수정해 관리자 권한 획득 시도
6. 로그아웃 후 이전 사용자 데이터 확인
각 시도가
어디에서 차단되어야 하는지 설명해줘.AI가 구현만 하는 도구에서
보안 검토 파트너
로 바뀌는 순간입니다.
이런 식으로 역할을 바꿔 사용하는 것도 바이브 코딩에서 꽤 유용합니다.
30. 현재 MyLog에는 관리자 기능을 만들지 않는다
여기까지 관리자 예제를 많이 들었지만 실제 MyLog에는 아직 관리자 기능을 넣지 않겠습니다.
현재 필요한 권한 모델은 아주 단순합니다.
로그인하지 않은 사용자
→ Record 접근 불가
로그인한 사용자
→ 자기 Record만 접근끝입니다.
이 정도면 충분합니다.
RBAC는 필요할 때 사용할 도구를 미리 이해한 것이지 지금 당장 적용해야 할 기능이 아닙니다.
오늘도 프로젝트 살찌우기 방지에 성공했습니다. 🥗
31. 오늘 MyLog 보안 구조를 다시 그려보자
지금까지 만든 구조는 이렇게 볼 수 있습니다.
사용자
↓
Supabase Auth
Authentication
"누구인가?"
↓
Session / JWT
↓
Flutter App
↓
Supabase Data API
↓
Postgres RLS
Authorization
"이 데이터에 접근해도 되는가?"
↓
Records이제 Auth와 RLS의 역할이 조금 더 명확해졌습니다.
로그인은 사용자를 식별하고,
RLS는 데이터 접근을 제한합니다.
두 가지가 함께 있어야 사용자별 클라우드 앱이 제대로 작동합니다.
32. 로그인 기능을 만들었다는 말의 의미도 달라졌다
예전에는
로그인 화면 있음
→ 로그인 기능 완료처럼 생각하기 쉬웠습니다.
이제는 조금 다르게 봅니다.
Authentication
로그인한 사용자가 누구인지 확인
Authorization
그 사용자가 허용된 데이터와 기능만 사용까지 보게 됩니다.
바이브 코딩에서 이런 시선이 굉장히 중요합니다.
AI는 로그인 화면을 몇 분 만에 만들 수 있습니다.
하지만
어디까지 접근할 수 있어야 하는지 결정하는 것은 제품을 만드는 사람의 몫입니다.
33. 오늘의 완료 조건
이번 편도 체크해보겠습니다.
[ ] Authentication과 Authorization의 차이를 설명할 수 있다.
[ ] 로그인 성공이 모든 권한을 의미하지 않는다는 것을 이해했다.
[ ] UI에서 버튼을 숨기는 것은 보안이 아니라는 것을 이해했다.
[ ] Record 접근이 RLS에서 실제로 제한되는지 확인했다.
[ ] auth.uid()와 user_id의 역할을 이해했다.
[ ] 사용자 A와 B의 데이터 접근을 다시 테스트했다.
[ ] 다른 사용자의 Record ID를 알아도 접근할 수 없어야 한다는 것을 이해했다.
[ ] SELECT와 INSERT 권한을 별도로 생각했다.
[ ] 앞으로 UPDATE와 DELETE에도 별도 권한 검토가 필요함을 확인했다.
[ ] 최소 권한 원칙을 이해했다.
[ ] Role과 Permission의 차이를 이해했다.
[ ] 현재 MyLog에는 불필요한 관리자 Role을 추가하지 않았다.
[ ] 사용자 수정 가능한 Metadata를 권한 판단에 사용하면 위험한 이유를 이해했다.
[ ] 로그아웃 후 이전 사용자 데이터가 화면에 남지 않는지 확인했다.
[ ] PROJECT_RULES.md에 Authorization 규칙을 추가했다.
[ ] AI에게 공격자 관점의 권한 검토를 요청해봤다.여기까지 됐다면 MyLog는
로그인할 수 있는 앱
에서
로그인한 사용자가 허용된 데이터만 사용할 수 있는 앱
으로 한 단계 더 나아갔습니다.
34. 그런데 정상적인 상황만 생각하면 또 문제가 생긴다
지금까지 우리가 만든 흐름을 떠올려보겠습니다.
로그인
↓
Record 요청
↓
정상 응답
↓
화면 표시모든 것이 참 평화롭습니다.
하지만 실제 앱은 이렇게만 움직이지 않습니다.
Supabase 응답이 늦을 수도 있고,
인터넷이 끊길 수도 있고,
Session이 만료될 수도 있고,
Record가 하나도 없을 수도 있고,
서버 오류가 날 수도 있고,
사용자가 저장 버튼을 두 번 누를 수도 있습니다.
제품은 정상 상태보다 정상이 아닐 때 어떻게 행동하는가에서 차이가 많이 납니다.
다음 편에서는 이 문제를 제대로 정리해보겠습니다.
다음 편
[바이브 코딩 실전 #18] 정상 작동만 확인하면 반드시 사고 납니다 | 로딩·오류·빈 화면 처리하기
다음 편에서는 화면 하나를 만들 때
정상하나만 생각하지 않고
Loading
Success
Empty
Error네 가지 상태를 기본으로 보는 습관을 만들어보겠습니다.
AI가
구현 완료했습니다.
라고 할 때 이제 우리는 한마디 더 물을 수 있게 됩니다.
"실패하면 화면에 뭐가 나오는데?"
이 질문부터 앱이 조금 더 질겨집니다. 🛡️📱
연재 기준: 2026년 8월현재 Supabase는 Auth와 PostgreSQL RLS를 결합한 Authorization 구조를 공식적으로 지원하며 Custom Claims와 Access Token Hook을 이용한 RBAC 방식도 제공하고 있습니다. 특히 사용자 스스로 수정할 수 있는 raw_user_meta_data를 권한 판단 기준으로 사용하는 것은 권장되지 않습니다. 실제 Role이나 관리자 권한을 도입할 때는 적용 시점의 Supabase 및 PostgreSQL 공식 보안 문서를 다시 확인하는 것이 좋습니다.
#바이브코딩 #VibeCoding #AI코딩 #Flutter #Supabase #Authentication #Authorization #인증 #인가 #권한관리 #RLS #RowLevelSecurity #RBAC #앱보안 #최소권한 #비개발자앱개발 #MyLog #바이브코딩실전
