> [!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)