ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Supabase][보안] publishable key와 secret key와 service_role key를 브라우저·SSR·Edge Functions에서 언제 나누나
    기타개발지식/풀스택개발 2026. 7. 31. 20:12

    IT 리서치 노트

    [Supabase][보안] publishable key와 secret key와 service_role key를 브라우저·SSR·Edge Functions에서 언제 나누나

    Supabase를 붙일 때 키 종류를 브라우저 키와 서버 키 정도로만 나누면 금방 막히는 지점이 생긴다. 2026년 7월 31일 기준 Supabase 공식 문서를 다시 보면 publishable과 secret key가 기존 anon·service_role과 병행 동작하고, secret·service_role은 RLS를 우회하며, 실제 권한 판정은 Authorization 헤더 흐름에 크게 좌우된다. 이 글은 publishable key, secret key, service_role key를 브라우저·SSR·Edge Functions에서 언제 나눠 두는 편이 덜 꼬이는지 정리한 것이다.

    1. 개요

    결론부터 말하면 브라우저는 publishable key, 관리자 백엔드와 배치는 secret 또는 service_role, Edge Functions와 SSR은 요청 목적에 따라 사용자용 client와 관리자용 client를 따로 두는 편이 안전하다. key 한 종류로 모든 경로를 덮으려 하면 RLS, 세션, Authorization 헤더가 한꺼번에 섞인다.

    Supabase는 2026년 말까지 legacy anon·service_role을 대체할 publishable·secret key 사용을 권장하고 있다. 따라서 지금 필요한 결정은 단순 교체가 아니라, 어떤 실행 환경을 public component로 보고 어떤 경로를 developer-controlled backend로 볼지 먼저 나누는 일이다.

    2. 어디서 실제로 막히는가

    실무에서 먼저 꼬이는 지점은 세 가지다. 첫째, 브라우저와 SSR이 같은 helper를 써서 secret 또는 service_role이 간접적으로 섞인다. 둘째, service role client를 만든 뒤 사용자 세션이나 쿠키가 Authorization 헤더를 덮어써 RLS 우회 여부를 잘못 추정한다. 셋째, Edge Functions에서 사용자용 읽기와 관리자용 쓰기를 같은 client 객체로 처리해 로그만 보고는 권한 축을 분리하지 못한다.

    Supabase 문서는 API key가 사용자를 식별하는 것이 아니라 애플리케이션 구성요소를 식별한다고 설명한다. publishable key는 web page, mobile app, CLI처럼 공개 컴포넌트용이고, secret key와 service_role은 서버, 이미 보호된 API, Edge Functions, 배치처럼 개발자가 통제하는 컴포넌트용이다. 이 전제를 놓치면 RLS 정책을 잘 써도 출발 key부터 잘못 잡는다.

    여기에 troubleshooting 문서가 덧붙이는 핵심이 있다. RLS는 apikey 헤더가 아니라 Authorization 헤더를 기준으로 해석된다. 즉 SSR에서 service role client를 만들어도 사용자 세션이 들어오면 user JWT가 Authorization을 덮을 수 있고, 반대로 관리자 배치에서 service role Authorization을 넣으면 항상 RLS를 우회한다.

    • 증상: SSR과 브라우저의 같은 쿼리가 서로 다른 row를 본다.
    • 실패: secret key만 백엔드에 있으면 모든 서버 경로가 같은 권한일 것이라고 가정한다.
    • 막힘: Edge Function이 사용자용인지 관리자용인지 코드 구조에서 구분하지 않는다.
    • 누락: createClient에 넣은 apikey와 실제 Authorization 헤더를 분리해서 보지 않는다.
    상황 먼저 볼 곳 판단 기준
    브라우저에 key를 넣는다 publishable/anon 여부 secret/service_role이면 구조를 다시 잡는다
    SSR에서 권한이 예상과 다르다 Authorization 헤더와 쿠키 세션 service role client와 사용자 세션 공유 여부를 본다
    Edge Function이 관리자 작업도 한다 client 분리 사용자용 client와 admin client를 따로 둔다

    3. 실무에서 적용하는 순서

    실무 적용 순서는 다섯 단계가 가장 실용적이다. 먼저 public component를 브라우저, 모바일 앱, 배포된 데스크톱 앱처럼 secret을 숨길 수 없는 환경으로 고정한다. 다음으로 SSR과 Edge Functions는 user-facing path와 admin path를 분리한다. 세 번째로 관리자 배치와 내부 API만 secret 또는 service_role을 쓰게 한다. 네 번째로 legacy anon·service_role만 있는 프로젝트라면 publishable·secret key를 추가 생성한 뒤 새 경로부터 옮긴다. 마지막으로 Authorization 헤더가 실제로 무엇이 되었는지 로그 기준을 남긴다.

    1. 브라우저 경로는 publishable 또는 anon만 허용한다.
    2. SSR helper에서 관리자 key를 분리한 별도 client를 만든다.
    3. Edge Functions는 사용자용과 관리자용 client를 한 함수 안에서도 명시적으로 나눈다.
    4. 배치와 관리자 API는 secret 또는 service_role을 쓰되 로그 마스킹과 rotate 절차를 둔다.
    5. migration 중에는 legacy key와 새 key가 동시에 살아 있다는 점을 문서화한다.

    이 구조를 잡아 두면 '브라우저는 publishable, 관리자 백엔드는 secret/service_role'이라는 큰 분기 아래에서 세부 문제가 훨씬 빨리 잘린다. 특히 SSR과 Edge Functions는 둘 다 서버 쪽 코드처럼 보이지만, 사용자 세션이 자동으로 섞이는 경로와 관리자 우회 경로를 같은 abstraction에 숨기면 나중에 권한 버그를 찾기 어렵다.

    운영 메모 예시
    runtime=browser|ssr|edge|job
    client_kind=publishable|secret|service_role
    authorization_source=apikey|user_jwt|manual_override
    rls_expected=respect|bypass
    session_shared=true|false
    rotation_group=public-ui|admin-api|queue-worker

    이미 invite 수락 뒤 token_hash와 첫 비밀번호 설정 글처럼 auth 흐름이 있는 화면은 publishable key가 더 자연스럽고, 관리자 초대 발송이나 백오피스 처리처럼 사용자를 대신하는 작업만 관리자 key로 빼는 편이 유지보수에 유리하다.

    4. 공식 문서와 예시 화면으로 확인하기

    첫 공식 화면은 Supabase API keys 문서의 타입 표다. 여기서 먼저 봐야 할 점은 key가 사용자를 구분하는 것이 아니라 애플리케이션 구성요소를 구분한다는 전제다.

    Supabase는 publishable, secret, anon, service_role 네 종류의 key와 권한 범위를 표로 나눠 설명한다.
    Supabase는 publishable, secret, anon, service_role 네 종류의 key와 권한 범위를 표로 나눠 설명한다.

    즉 브라우저인지, 서버인지, Edge Function인지부터 분리하지 않으면 RLS와 세션 해석이 곧바로 섞인다. 이 글은 그 분기를 먼저 고정하는 허브 글에 가깝다.

    두 번째 자료는 가장 강한 금지선이다. Supabase는 secret과 service_role이 RLS를 우회하므로 프론트엔드에 노출하면 안 된다고 분명히 적는다.

    Supabase secure-data 문서는 secret key와 service_role key를 프론트엔드에 노출하지 말라고 직접 경고한다.
    Supabase secure-data 문서는 secret key와 service_role key를 프론트엔드에 노출하지 말라고 직접 경고한다.

    publishable key까지 같은 층으로 묶어 겁낼 필요는 없지만, secret과 service_role을 브라우저에서 다루면 안 된다는 선은 흔들리면 안 된다.

    세 번째 공식 화면은 service role troubleshooting 문서다. 여기서는 많은 팀이 놓치는 한 줄이 있다. RLS는 apikey가 아니라 Authorization 헤더 기준으로 해석된다는 점이다.

    Supabase troubleshooting 문서는 Authorization 헤더가 service role이면 항상 RLS를 우회한다고 설명한다.
    Supabase troubleshooting 문서는 Authorization 헤더가 service role이면 항상 RLS를 우회한다고 설명한다.

    그래서 SSR이나 Edge Functions에서 service role client를 만든 뒤 사용자 세션이 섞이면 기대한 권한과 실제 권한이 달라질 수 있다. key 종류만 보는 글이 아니라 헤더 흐름까지 봐야 하는 이유다.

    네 번째 자료는 실행 환경 기준 분리표다. 브라우저, SSR, Edge Functions를 한 표에 두면 어떤 key를 어디서 시작해야 하는지 훨씬 빨리 정리된다.

    브라우저, SSR, Edge Functions, 관리자 배치를 key 종류와 RLS 기대치로 나눈 표다.
    브라우저, SSR, Edge Functions, 관리자 배치를 key 종류와 RLS 기대치로 나눈 표다.

    이미 service_role key를 브라우저에 넣으면 안 되는 글이 한 금지선을 다뤘다면, 이번 표는 그보다 넓게 전체 key 배치를 설계하는 단계다.

    다섯 번째 자료는 코드 분리 예시다. key를 바꾸는 것보다 먼저 client 객체를 브라우저용, 사용자 세션용, 관리자용으로 분리하는 편이 사고가 적다.

    브라우저용 publishable client와 서버용 admin client를 분리한 예시다.
    브라우저용 publishable client와 서버용 admin client를 분리한 예시다.

    service role client를 auth 처리나 SSR helper와 같은 추상화 안에 감추면 나중에 Authorization 헤더가 어디서 덮였는지 추적하기 어려워진다.

    마지막 자료는 현장 점검 순서다. 새 key로 옮길 때는 값 생성보다 어느 실행 환경에 어떤 client가 붙는지 먼저 메모해야 migration이 덜 꼬인다.

    publishable, secret, service_role key 전환과 런타임 분리를 위한 체크리스트다.
    publishable, secret, service_role key 전환과 런타임 분리를 위한 체크리스트다.

    또 RLS와 Authorization 헤더 글을 같이 보면 배치 후 실제 헤더 검증 순서까지 바로 이어진다.

    5. 주의사항과 리스크

    첫 번째 리스크는 secret key가 브라우저에서 401이 나므로 '유출돼도 괜찮다'고 오해하는 것이다. 문서는 브라우저에서 막히더라도 다른 도구에서는 악용될 수 있으니 바로 삭제하라고 적고 있다. 두 번째 리스크는 SSR helper가 사용자 세션을 공유한다는 사실을 잊고 service role client를 섞는 것이다. 세 번째 리스크는 Edge Functions에서 사용자용 client와 관리자용 client를 같이 써 놓고 로그에는 하나의 함수 이름만 남겨 두는 것이다.

    운영 전에는 최소한 key 종류, 실행 환경, Authorization 출처, RLS 기대치 네 항목을 같은 표로 남겨 두는 편이 좋다. 그래야 rotate, 권한 버그, 배치 오류, 세션 override를 같은 사건으로 섞지 않는다.

    • secret/service_role은 프론트엔드에 절대 두지 않는다.
    • SSR과 Edge Functions는 사용자 세션 공유 여부를 먼저 확인한다.
    • legacy key와 새 key가 병행 동작한다는 점을 migration 문서에 명시한다.

    6. 결론

    Supabase key 분리는 단순히 새 publishable·secret key로 이름을 바꾸는 작업이 아니다. 브라우저, SSR, Edge Functions, 관리자 배치가 각각 public component인지 developer-controlled backend인지 먼저 나누고, 그 위에 Authorization 헤더와 RLS 기대치를 얹어야 운영이 덜 꼬인다.

    프로젝트가 아직 legacy anon·service_role 중심이라면 다음 단계는 실제 cutover 순서를 정하는 일이다. 어떤 경로부터 publishable·secret으로 바꾸고 legacy key를 언제 끄는지가 필요한 경우에는 legacy key migration 순서 글로 이어서 보는 편이 맞다.

    • 브라우저는 publishable 또는 anon으로 고정한다.
    • SSR과 Edge Functions는 사용자용 client와 관리자용 client를 분리한다.
    • secret/service_role은 백엔드에서만 쓰고 rotate 계획을 남긴다.

    7. 참고 링크

    1. https://supabase.com/docs/guides/getting-started/api-keys
    2. https://supabase.com/docs/guides/database/secure-data
    3. https://supabase.com/docs/guides/functions/secrets
    4. https://supabase.com/docs/guides/troubleshooting/why-is-my-service-role-key-client-getting-rls-errors-or-not-returning-data-7_1K9z
Designed by Tistory.