인증(Authentication)을 정리하고 나면, 곧바로 더 어려운 문제가 등장합니다. 바로 “이 사용자가 이 작업을 해도 되는가”를 어떻게 판단할 것인가입니다.
실무에서는 다음과 같은 질문이 반복됩니다.
- 관리자, 일반 사용자 말고 중간 권한이 생기면 어떻게 확장하지?
- 화면에서는 막았는데, API에서는 막히지 않는 경우는 왜 생길까?
- 권한 로직이 여기저기 흩어져 있어서 수정이 너무 어렵다
권한 설계는 단순히 if 문을 추가하는 문제가 아닙니다. 초기에 기준 없이 붙이기 시작하면, 기능이 늘어날수록 보안 리스크와 유지보수 비용이 함께 커집니다.
이번 글에서는 권한을 “조건문”이 아니라 모델링 대상으로 다루는 실무 기준을 정리합니다.
- 역할(Role) 기반 권한의 한계와 사용 기준
- 정책(Policy) 기반 권한이 필요한 시점
- 리소스(Resource) 중심 권한 모델링 방법
- Next.js + TypeScript에서 권한을 코드로 고정하는 방법
개념/배경 설명: 권한은 왜 점점 복잡해지는가
대부분의 서비스는 이렇게 시작합니다.
- ADMIN은 다 가능
- USER는 일부만 가능
하지만 운영을 하다 보면 요구사항이 빠르게 늘어납니다.
- 읽기만 가능한 사용자
- 자기 데이터만 수정 가능한 사용자
- 팀 단위로 관리 권한이 다른 사용자
이때 역할만 늘려서 대응하면, 곧 이런 문제가 발생합니다.
- 역할 이름이 의미를 잃는다
- 역할 조합이 폭발적으로 늘어난다
- “왜 이 역할은 이걸 못 해요?”라는 질문에 답하기 어렵다
그래서 실무에서는 권한을 다음 세 요소로 분리해서 생각합니다.
- 누가(Subject)
- 무엇을(Resource)
- 어떤 행동을(Action)
핵심 설계 1: Role 기반 권한의 올바른 사용 범위
Role 기반 권한은 가장 단순하고 직관적입니다. 그래서 “범위가 좁을 때”는 여전히 좋은 선택입니다.
권장되는 Role 사용 기준은 다음과 같습니다.
- 역할 수가 적고(2~4개)
- 역할 간 권한 차이가 명확하며
- 리소스 단위 제어가 필요 없는 경우
type Role = 'ADMIN' | 'USER';
function canAccessAdmin(role: Role): boolean {
return role === 'ADMIN';
}
이 방식은 단순하지만, “자기 데이터만 수정 가능” 같은 요구사항이 나오면 바로 한계에 부딪힙니다.
실무 포인트 정리
- Role은 거칠게 나눈다
- Role이 늘어나기 시작하면 구조를 의심한다
- Role로 모든 문제를 풀려고 하지 않는다
핵심 설계 2: Policy 기반 권한으로 확장하기
Policy 기반 권한은 “조건”을 코드로 표현하는 방식입니다. 역할이 아니라, 규칙 자체가 권한의 기준이 됩니다.
type PolicyContext = {
userId: string;
role: Role;
};
type Resource = {
ownerId: string;
};
function canEditResource(ctx: PolicyContext, resource: Resource): boolean {
if (ctx.role === 'ADMIN') return true;
return ctx.userId === resource.ownerId;
}
이 방식의 장점은 명확합니다.
- 역할 수를 늘리지 않아도 된다
- “왜 허용/차단됐는지” 설명이 가능하다
- 비즈니스 규칙을 그대로 표현할 수 있다
반면, Policy가 여기저기 흩어지면 관리가 어려워집니다. 그래서 Policy는 반드시 “한 곳”에 모아야 합니다.
실무 포인트 정리
- Policy는 함수로 표현한다
- 화면/라우트에 직접 쓰지 않는다
- 도메인 기준으로 묶어 관리한다
핵심 설계 3: Resource 중심 권한 모델링
권한 설계를 가장 안정적으로 만드는 기준은 “리소스 중심 사고”입니다.
권한 질문을 이렇게 바꿔보면 구조가 명확해집니다.
- 이 사용자가 이 리소스를 볼 수 있는가?
- 이 사용자가 이 리소스를 수정할 수 있는가?
type Action = 'READ' | 'UPDATE' | 'DELETE';
function authorize(
action: Action,
ctx: PolicyContext,
resource: Resource,
): boolean {
switch (action) {
case 'READ':
return true;
case 'UPDATE':
return ctx.role === 'ADMIN' || ctx.userId === resource.ownerId;
case 'DELETE':
return ctx.role === 'ADMIN';
}
}
이 구조를 쓰면, UI·API·Server Action 어디에서든 같은 기준으로 권한을 판단할 수 있습니다.
실무 포인트 정리
- 권한 함수는 “행동 + 리소스”를 기준으로 한다
- 리소스 없는 권한 체크는 최소화한다
- 권한 판단 로직은 재사용 가능해야 한다
Next.js + TypeScript에서 권한을 배치하는 위치
권한 체크 위치가 흔들리면 보안 사고로 직결됩니다. 실무 기준은 다음과 같습니다.
- UI: 권한에 따라 버튼/메뉴를 숨김
- Server Action / API Route: 반드시 권한을 다시 검증
- 공통 Policy 모듈: 단일 권한 기준
UI 권한 체크는 “편의”일 뿐, 보안은 항상 서버에서 보장해야 합니다.
운영/실무에서 자주 겪는 문제
- 화면에서는 막혔는데 API에서는 허용되는 문제
- 권한 조건이 화면마다 달라 예외가 발생하는 문제
- Role이 7~8개로 늘어나 의미를 잃은 경우
- 권한 변경 시 어디를 고쳐야 할지 모르는 구조
실무 권장 체크리스트
- 권한 판단 기준이 Role에만 의존하고 있지 않은가
- Policy 함수가 한 곳에 모여 있는가
- 리소스 기준 권한 질문으로 표현할 수 있는가
- UI와 서버가 동일한 권한 기준을 사용하는가
- 권한 로직 변경 시 영향 범위를 예측할 수 있는가
권한 설계의 핵심은 “막는다”가 아니라 설명 가능하게 만든다는 데 있습니다.
역할은 단순하게, 정책은 명확하게, 리소스는 중심에 두면 권한은 더 이상 얽히는 코드가 아니라 비즈니스 규칙을 담는 구조가 됩니다.
'개발 > Typescript' 카테고리의 다른 글
| [TYPESCRIPT] 백엔드 인터페이스 설계 기준: 프론트엔드와 계약을 안정적으로 유지하는 방법 (0) | 2026.01.20 |
|---|---|
| [TYPESCRIPT] Nextjs 권한 테스트 전략: Authorization 로직을 테스트로 고정 방법 (0) | 2026.01.19 |
| [TYPESCRIPT] Next.js Server Actions vs API Route 선택 전략: 권한·트랜잭션·에러 처리 관점 비교 (0) | 2026.01.17 |
| [TYPESCRIPT] Next.js 데이터 접근 계층 설계: fetch 래퍼·에러 표준화·캐시 전략 (0) | 2026.01.16 |
| [TYPESCRIPT] Next.js + TypeScript 프로젝트 구성 전략: 운영과 유지보수를 고려한 폴더·설정·규칙 (0) | 2026.01.15 |
