> [!date] published: 2025-06-06
## 문제상황
Next.js Route Handler에서 Supabase에 데이터를 INSERT하려고 할 때 **401 Unauthorized 에러**가 발생하는 문제입니다.
### 에러가 발생한 환경
- Next.js Route Handler (서버 사이드)
- `@supabase/ssr`의 `createServerClient` 사용
- RLS(Row Level Security)가 활성화된 테이블
### 초기 코드 예시
```typescript
// Route Handler에서 INSERT 시도
const supabase = await createClient();
await supabase.from("memo").insert({
id: crypto.randomUUID(),
title,
content,
});
// 결과: 401 Unauthorized 에러 발생
```
## 문제를 해결하기 위해 공부한 것들
### 1. Supabase의 두 가지 API 키 이해
**ANON_KEY (공개키)**
- 클라이언트에서 주로 사용
- RLS 정책의 영향을 받음
- 브라우저에 노출되어도 안전
**SERVICE_ROLE_KEY (비밀키)**
- 서버에서만 사용해야 함
- RLS 정책을 우회할 수 있음
- 모든 데이터베이스 권한 보유
### 2. RLS(Row Level Security) 작동 원리
RLS는 테이블 단위로 설정되는 보안 기능입니다:
```sql
-- RLS 상태 확인
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE tablename = 'your_table_name';
```
- `rowsecurity: true` → RLS 활성화
- `rowsecurity: false` → RLS 비활성화
### 3. RLS 정책(Policy) 개념
RLS가 활성화되면 **정책이 없는 경우 기본적으로 모든 작업이 차단**됩니다.
```sql
-- 현재 정책 확인
SELECT * FROM pg_policies WHERE tablename = 'your_table_name';
```
### 4. 사용자 역할(Role) 시스템
Supabase에서 주요 역할:
- `anon`: 익명 사용자 (로그인하지 않은 상태)
- `authenticated`: 인증된 사용자 (로그인한 상태)
JWT 토큰의 `role` 필드로 현재 역할을 확인할 수 있습니다.
## 해결 방법
### 방법 1: RLS 정책 생성 (권장)
가장 안전하고 권장되는 방법입니다.
```sql
-- anon 사용자도 INSERT 허용
CREATE POLICY "Enable insert for anon users" ON memo
FOR INSERT
TO anon
WITH CHECK (true);
-- authenticated 사용자만 INSERT 허용
CREATE POLICY "Enable insert for authenticated users" ON memo
FOR INSERT
TO authenticated
WITH CHECK (true);
-- 모든 사용자에게 INSERT 허용
CREATE POLICY "Enable insert for all users" ON memo
FOR INSERT
TO anon, authenticated
WITH CHECK (true);
```
### 방법 2: Service Role Key 사용
Route Handler에서 Service Role Key를 사용하여 RLS를 우회합니다.
```typescript
import { createClient } from "@supabase/supabase-js";
// .env.local에 SUPABASE_SERVICE_ROLE_KEY 추가 필요
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!, // Service Role Key 사용
{
auth: { persistSession: false },
}
);
```
**Service Role Key 찾는 방법:**
1. Supabase 대시보드 → Settings → API
2. "service_role" 키를 복사
3. `.env.local`에 `SUPABASE_SERVICE_ROLE_KEY=...` 추가
### 방법 3: RLS 비활성화 (임시 해결책)
개발 단계에서만 사용하는 임시 방법입니다.
```sql
-- RLS 비활성화 (보안상 위험하므로 프로덕션에서는 사용 금지)
ALTER TABLE memo DISABLE ROW LEVEL SECURITY;
```
### 에러 디버깅을 위한 개선된 코드
```typescript
const { data, error } = await supabase.from("memo").insert({
id: crypto.randomUUID(),
title,
content,
});
if (error) {
console.error("Supabase insert error:", error);
return NextResponse.json(
{ message: `데이터베이스 오류: ${error.message}` },
{ status: 500 }
);
}
```
## 정책 관리
### 정책 확인 및 수정
```sql
-- 현재 정책 목록 조회
SELECT * FROM pg_policies WHERE tablename = 'memo';
-- 정책 삭제
DROP POLICY "policy_name" ON memo;
-- 정책은 언제든 추가/수정/삭제 가능
```
### 일반적인 발전 과정
1. **개발 초기**: anon 사용자에게 모든 권한 (빠른 프로토타이핑)
2. **사용자 시스템 도입**: authenticated 사용자만 허용
3. **세밀한 권한 제어**: 사용자별, 역할별 권한 분리
## 관련 링크
- [Supabase RLS 공식 문서](https://supabase.com/docs/guides/auth/row-level-security)
- [Supabase API Keys 가이드](https://supabase.com/docs/guides/api/api-keys)
- [Next.js에서 Supabase 사용하기](https://supabase.com/docs/guides/getting-started/quickstarts/nextjs)
- [Supabase SSR 패키지 문서](https://supabase.com/docs/guides/auth/server-side/nextjs)