앱에 역할 기반 접근 제어(RBAC)를 추가하는 방법
역할 기반 접근 제어를 추가하려면 각 사용자에게 하나 이상의 역할(예: 관리자, 편집자, 뷰어)을 부여하고, 각 역할이 할 수 있는 작업을 정의한 뒤, 모든 요청에서 그 권한을 서버에서 확인하세요. 버튼을 숨기는 것만으로는 안 됩니다. 접근하는 레코드가 사용자의 계정이나 팀에 속하는지도 확인해야 합니다. 데이터베이스에 고정된 역할 몇 개로 시작하고, 다른 사용자의 데이터에 직접 접근해 보며 테스트하세요.
인증과 인가
자주 혼동되는 두 단어가 있습니다.
- 인증은 *당신은 누구입니까?*에 답하는 것으로, 로그인입니다. 앱에 로그인을 추가하는 방법을 참고하세요.
- 인가는 *무엇을 할 수 있습니까?*에 답하는 것으로, 로그인한 사용자가 접근할 수 있는 페이지, 레코드, 작업을 정합니다.
RBAC는 인가를 구현하는 가장 흔한 방식입니다. 중요한 이유가 있습니다. 2026년 9월 기준 깨진 접근 제어(Broken Access Control)는 OWASP Top 10:2025에서 1위이며, OWASP는 웹 애플리케이션의 가장 심각한 보안 위험을 정리한 널리 쓰이는 목록입니다. 대표적인 실패는 URL의 ID를 바꿔서 다른 사용자의 레코드를 읽거나, 일반 사용자가 관리자 전용 API 라우트를 호출하는 것입니다.
모델 선택
| 모델 | 동작 방식 | 적합한 경우 | 트레이드오프 |
|---|---|---|---|
| 단일 역할 열 | users.role = admin / member | 역할이 두세 개인 소규모 앱 | 사람마다 장소에 따라 다른 역할이 필요해지면 복잡해집니다 |
| 역할 + 권한 | 역할을 이름 있는 권한(invoices.approve)에 대응시키고, 코드는 권한을 확인합니다 | 규칙이 계속 늘어날 앱 | 테이블이 더 많아지고, 권한 목록을 관리해야 합니다 |
| 팀별(멀티 테넌트) 역할 | 멤버십 테이블: 사용자 + 조직 + 역할 | 사람들이 팀이나 워크스페이스에 속하는 SaaS | 모든 쿼리가 조직 범위로 제한되어야 합니다 |
| 속성 또는 관계 기반 | 레코드 속성에 대한 규칙("작성자는 자신의 글을 수정할 수 있다") 또는 관계에 대한 규칙("공유된 대상") | 문서, 공유, 복잡한 워크플로 | 이해하기 어려우며, 전용 라이브러리나 서비스를 고려하세요 |
| 인증 제공업체의 역할 | 로그인 제공업체가 역할이나 조직 멤버십을 저장합니다 | 이미 호스팅 인증 서비스를 쓰는 앱 | 해당 제공업체에 종속되며, 그래도 서버에서 강제해야 합니다 |
실제로 대부분의 앱은 두 가지를 조합합니다. 어떤 종류의 작업을 할 수 있는지를 정하는 역할, 그리고 어떤 레코드에 그 작업을 할 수 있는지를 정하는 소유권입니다. "편집자는 글을 편집할 수 있다"는 역할 규칙이고, "…자신의 워크스페이스에 있는 글"은 소유권 규칙입니다. 둘 다 필요합니다.
처음에는 생각보다 단순하게 시작하세요. 관리자, 멤버, 뷰어만으로도 놀라울 만큼 많은 앱이 충분합니다. 역할 열 개를 아무도 설명하지 못하는 상태로 만들어 두고 나중에 풀기보다, 나중에 역할을 나누는 편이 쉽습니다.
코드를 쓰기 전에 역할 설계하기
먼저 권한 표를 평이한 말로 작성하세요. 이것은 AI를 위한 명세이자 테스트 계획이 됩니다.
| 작업 | 소유자 | 관리자 | 멤버 | 뷰어 |
|---|---|---|---|---|
| 팀의 프로젝트 보기 | ✓ | ✓ | ✓ | ✓ |
| 프로젝트 생성 및 편집 | ✓ | ✓ | ✓ | |
| 프로젝트 삭제 | ✓ | ✓ | ||
| 사람 초대 및 제거 | ✓ | ✓ | ||
| 역할 변경 | ✓ | |||
| 결제 | ✓ |
예외 상황도 정하세요. 마지막 소유자는 나갈 수 있는가? 관리자는 소유자를 제거할 수 있는가? 제거된 사용자는 무엇을 보게 되는가?
단계별 진행
1. 먼저 로그인이 제대로 동작하게 하기
RBAC는 인증 위에 놓입니다. 서버는 각 요청을 보낸 사람이 누구인지 확실히 알아야 하며, 그 정보는 세션이나 검증된 토큰에서 와야 합니다. 요청 본문에서 브라우저가 보낸 사용자 ID를 믿어서는 안 됩니다.
2. 역할을 데이터베이스에 저장하기
단일 팀 앱이라면 사용자 테이블의 role 열로 충분합니다. 팀 단위라면 멤버십 테이블을 쓰세요.
create table memberships (
user_id uuid references users(id),
organization_id uuid references organizations(id),
role text not null check (role in ('owner','admin','member','viewer')),
primary key (user_id, organization_id)
);
사용자가 일반 프로필 업데이트로 자기 역할을 바꾸게 해서는 안 됩니다. 역할 변경은 권한이 확인되는 전용 엔드포인트로만 처리하세요.
3. 서버에서 한곳에 모아 권한 확인하기
can(user, 'projects.delete', project) 같은 헬퍼 함수 하나를 만들고, 데이터를 읽거나 바꾸는 모든 API 라우트, 서버 액션, 백그라운드 작업에서 호출하세요. 확인 로직이 코드 곳곳에 흩어져 있는 것보다 한곳에 모여 있는 편이 검토와 테스트가 쉽습니다. 기본은 거부입니다. 어떤 규칙도 허용하지 않는 작업은 거절하세요.
4. 모든 쿼리를 범위로 제한하기
팀 데이터를 조회하는 모든 쿼리에는 팀 조건이 들어가야 합니다. where id = :id만 쓰지 말고 where organization_id = :currentOrg처럼 쓰세요. 이것이 /projects/123을 /projects/124로 바꿔서 다른 회사의 프로젝트를 보는 일을 막습니다. 다른 팀의 레코드에는 "권한 없음"이 아니라 "찾을 수 없음"을 반환하세요. 그래야 그 레코드가 존재한다는 사실조차 드러나지 않습니다.
5. 중요한 곳에는 데이터베이스 수준 보호 추가하기
Postgres의 행 수준 보안(RLS)은 데이터베이스가 사용자별로 볼 수 있는 행을 강제하게 해 줍니다. 브라우저가 데이터베이스와 직접 통신하는 경우(일부 호스팅 백엔드처럼)에는 필수이고, 그 외에도 민감한 테이블을 위한 유용한 2차 계층입니다.
6. 인터페이스에 역할 반영하기
이제 사용자가 할 수 없는 작업을 숨기거나 비활성화하세요. 앱이 헷갈리지 않도록 하기 위한 것이며, 이는 사용성만을 위한 것입니다. 실제 보호는 서버 검사가 합니다.
7. 민감한 작업 기록하기
누가 역할을 바꾸었는지, 데이터를 삭제하거나 내보냈는지, 그리고 언제 그랬는지 기록하세요. 감사 로그는 고객 지원 문의와 보안 사고에 도움이 됩니다.
8. 공격자처럼 테스트하기
각 역할로 로그인해서 권한 표의 모든 작업을 시도해 보세요. 그다음 동작하면 안 되는 것들을 시도하세요. 뷰어로 관리자 API 라우트를 호출하기, URL과 요청에서 ID 바꾸기, 마지막 소유자 자신을 제거하기 등입니다. 규칙이 안정되면 권한 표에 대한 자동 테스트를 작성할 가치가 있습니다.
효과가 있는 프롬프트
역할 기반 접근 제어를 추가해 주세요. 조직별 역할은 소유자, 관리자, 멤버, 뷰어이며 memberships 테이블에 저장합니다. 권한은 다음 표에 있습니다: [권한 표 붙여넣기]. 서버에 can(user, action, resource) 헬퍼 하나를 만들고, 기본은 거부로 하며, 모든 API 라우트에서 사용해 주세요. 모든 쿼리는 organization_id로 범위를 제한하세요. 사용자가 할 수 없는 작업은 UI에서 숨기되, 보안을 그것에 의존하지 마세요.
이 앱의 모든 API 라우트와 서버 함수를 검토해 주세요. 각각에 대해 어떤 데이터를 읽거나 바꾸는지, 로그인 여부를 확인하는지, 역할을 확인하는지, 쿼리를 사용자의 조직으로 범위 제한하는지 목록으로 정리해 주세요. 확인이 빠진 곳은 고치고, 목록을 보여 주세요.
소유자가 멤버의 역할을 바꿀 수 있는 관리자 전용 /admin 페이지를 추가해 주세요. 마지막 소유자를 제거하거나 강등하는 것은 막고, 모든 역할 변경을 누가 언제 했는지와 함께 기록해 주세요. 소유자가 아닌 사람이 역할 변경 엔드포인트를 호출하면 거부해 주세요.
흔한 실수
- 프런트엔드에서만 역할을 확인함. API는 여전히 누구나 호출할 수 있습니다.
- 클라이언트가 보낸 ID를 신뢰함. 세션이 아니라 요청 본문에 있는
userId나role을 믿는 경우입니다. - 범위가 없는 쿼리. 레코드 ID만으로 조회합니다.
- 페이지에서만 역할을 확인하고 API 라우트, 내보내기, 파일 다운로드, 웹훅에서는 확인하지 않음.
- 코드에 관리자 이메일 주소를 하드코딩함(
if email === 'me@...'). 잊기 쉽고 바꾸기도 어렵습니다. - 너무 이르게 역할을 너무 많이 만듦. 여섯 달 뒤에 아무도 설명하지 못하게 됩니다.
- 오래된 세션. 강등된 사용자가 로그아웃하기 전까지 관리자 권한을 유지합니다. 민감한 작업에서는 데이터베이스에서 역할을 확인하거나 새로 고치세요.
RBAC 체크리스트
- 권한 표를 평이한 말로 작성했고, 예외 상황을 정했다
- 서버는 요청 데이터가 아니라 세션에서 사용자를 확인한다
- 역할은 데이터베이스에 저장되며, 사용자가 자신의 역할을 바꿀 수 없다
- 하나의 중앙 권한 확인 함수를 기본 거부로 두고, 모든 라우트에서 사용한다
- 모든 쿼리가 사용자의 조직이나 소유권으로 범위가 제한된다
- 브라우저가 데이터베이스에 접근할 수 있다면 행 수준 보안을 적용했다
- UI는 사용자가 할 수 없는 작업을 숨긴다(사용성만을 위한 것)
- 민감한 작업이 기록된다
- 모든 역할을 테스트했다. 직접 API 호출과 바뀐 ID도 포함한다
접근 제어는 더 긴 목록의 한 항목입니다. 나머지는 AI로 만든 앱의 보안 체크리스트에서 다룹니다. 직원용 도구를 만들고 있다면, 내부 도구 만드는 법에서 역할이 어디에 들어가는지 보여 줍니다.
Mythex의 RBAC
Mythex는 앱 최종 사용자를 위한 로그인이나 역할 시스템을 기본 제공하지 않습니다. 인증 라이브러리나 제공업체를 선택하고 역할을 설명하면, 에이전트가 이를 코드와 프로젝트의 Postgres 데이터베이스에 연결합니다(앱에 로그인 추가하기). Mythex의 워크스페이스 역할(소유자, 멤버 등)은 Mythex 프로젝트를 편집할 수 있는 사람을 정하는 것이며, 여러분이 게시한 앱을 사용할 수 있는 사람을 정하지 않습니다. 만들기 전에 계획 모드로 권한 표에 합의하고, 위의 검토 프롬프트를 사용한 뒤, 게시하기 전에 미리보기에서 모든 역할을 테스트하세요. 문서의 앱 보안 프롬프트도 유용한 후속 자료입니다.
자주 묻는 질문
역할 기반 접근 제어란 무엇인가요?
역할 기반 접근 제어(RBAC)는 사용자가 가진 역할, 예를 들어 관리자, 편집자, 뷰어에 따라 각 사용자가 무엇을 할 수 있는지 정하는 방식입니다. 권한은 개인이 아니라 역할에 붙습니다. 그래서 누군가의 권한을 바꾸려면 그 사람의 역할을 바꾸면 됩니다.
인증과 인가는 어떻게 다른가요?
인증은 누구인지 확인하는 것으로, 보통 로그인을 말합니다. 인가는 로그인한 사람이 무엇을 보고 할 수 있는지 결정하는 것입니다. RBAC는 인가를 하는 한 가지 방법입니다.
인터페이스에서 버튼을 숨기는 것만으로 접근을 제한할 수 있나요?
아니요. 누구나 API를 직접 호출할 수 있으므로, 버튼을 숨기면 사람들에게 보이는 것만 바뀝니다. 데이터를 읽거나 바꾸는 모든 요청은 서버에서 사용자의 역할과 레코드 소유 여부를 확인해야 합니다.
Postgres 행 수준 보안(row-level security)을 써야 하나요?
행 수준 보안을 쓰면 데이터베이스 자체가 사용자별로 읽고 쓸 수 있는 행을 강제할 수 있습니다. 특히 프런트엔드가 데이터베이스와 직접 통신할 때 강력한 안전장치가 됩니다. 다만 복잡도가 높아지므로, 많은 앱은 권한을 서버 코드에서 강제하고, 민감한 테이블에만 행 수준 보안을 추가 계층으로 씁니다.