ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Supabase][보안] legacy anon·service_role에서 publishable·secret key로 옮길 때 어떤 순서로 교체하고 언제 끄나
    기타개발지식/풀스택개발 2026. 8. 1. 09:15

    IT 리서치 노트

    [Supabase][보안] legacy anon·service_role에서 publishable·secret key로 옮길 때 어떤 순서로 교체하고 언제 끄나

    Supabase 프로젝트를 오래 운영했다면 아직 anon과 service_role만 쓰는 경로가 남아 있을 가능성이 높다. 2026년 8월 1일 기준 Supabase 공식 문서를 다시 보면 publishable key와 secret key는 legacy key와 함께 바로 추가할 수 있고, 두 체계는 병행 동작한다. 이 글은 legacy anon·service_role에서 publishable·secret key로 옮길 때 어떤 순서로 교체하고 언제 기존 key를 끄는 편이 덜 위험한지 정리한 것이다.

    1. 개요

    결론부터 말하면 public code의 anon을 publishable로 먼저 바꾸고, backend의 service_role을 secret으로 옮긴 뒤, webhook과 SQL 경로를 마지막에 정리하는 편이 안전하다. 새 key와 legacy key는 동시에 동작하므로 무중단에 가깝게 순차 교체할 수 있다.

    migration의 핵심은 key 이름이 아니라 실행 위치와 헤더 흐름이다. 브라우저, SSR, Edge Functions, webhook을 따로 적어 두지 않으면 언제 legacy key를 꺼도 되는지 판단이 모호해진다.

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

    실무에서 가장 자주 막히는 지점은 세 가지다. 첫째, legacy key가 계속 동작하니 새 key 교체를 미루다가 경로가 더 늘어난다. 둘째, service_role을 secret으로 바꾸면서도 브라우저와 backend 경로를 다시 분리하지 않는다. 셋째, webhook이나 pg_net 호출처럼 Authorization Bearer에 service_role을 넣던 경로를 그대로 둔다.

    Supabase migration 가이드는 legacy key와 새 key가 동시에 동작하므로 경로별로 순차 교체하라고 설명한다. API keys 가이드는 key가 사용자를 구분하는 것이 아니라 애플리케이션 구성요소를 구분한다고 적는다. JWT 가이드는 publishable과 secret key가 on-the-fly로 짧은 JWT로 바뀐다고 설명한다. 이 셋을 같이 읽으면 migration은 런타임 재배치라는 점이 더 분명해진다.

    • 증상: 새 key를 만들었는데 어느 경로부터 바꿔야 할지 정리가 안 된다.
    • 실패: anon/service_role을 단순 치환하면 끝날 것이라고 본다.
    • 막힘: webhook과 SQL 호출의 헤더 방식이 달라진다는 점을 놓친다.
    • 누락: legacy key 의존 경로를 로그와 문서에 남기지 않는다.

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

    실무 순서는 다섯 단계가 효율적이다. 먼저 새 publishable·secret key를 생성하고 병행 기간을 문서화한다. 다음으로 public code의 anon을 publishable로 바꾼다. 세 번째로 backend와 SSR의 service_role을 secret으로 옮긴다. 네 번째로 webhook, pg_net, Edge Functions의 헤더와 client 분리를 다시 본다. 마지막으로 legacy key 의존이 사라진 뒤 기존 key를 비활성화한다.

    1. 새 key를 만들고 병행 기간을 정한다.
    2. public code를 anon에서 publishable로 바꾼다.
    3. backend를 service_role에서 secret으로 바꾼다.
    4. webhook과 pg_net 헤더를 다시 맞춘다.
    5. legacy key 사용량 0을 확인한 뒤 비활성화한다.

    구체적으로는 환경 변수 파일을 열어 anon 값을 조회하고, public code 저장소에서 publishable 값으로 교체하고, backend secret 저장 위치를 바꾸고, webhook 헤더를 실행 로그로 확인하고, dashboard와 배포 로그에서 legacy key 호출이 남았는지 다시 조회하는 식으로 기록하는 편이 좋다. 교체, 확인, 저장, 조회, 실행, 로그 검토를 같은 순서로 적어 두면 사람이 바뀌어도 cutover를 반복하기 쉽다.

    migration_log:
    browser_key=publishable
    backend_key=secret
    webhook_header=apikey
    legacy_anon_hits=0
    legacy_service_role_hits=0
    legacy_keys_disabled=false

    이 순서를 지키면 RLS 정책을 건드리지 않고도 큰 경로를 먼저 분리할 수 있다. 브라우저와 backend의 권한 축이 먼저 갈리고, 마지막에 legacy key 종료만 남는다. 특히 브라우저, 서버, Edge Functions, webhook 경로를 파일 단위와 로그 단위로 함께 확인해야 migration 종료 시점을 더 자신 있게 정할 수 있다.

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

    첫 공식 화면은 API key 타입 표다. 여기서 legacy key와 새 key가 역할상 어떻게 대응되는지부터 정리해야 migration 순서가 잡힌다.

    Supabase는 publishable, secret, anon, service_role 네 종류의 key와 사용 위치를 표로 설명한다.
    Supabase는 publishable, secret, anon, service_role 네 종류의 key와 사용 위치를 표로 설명한다.

    즉 migration은 이름 바꾸기가 아니라 실행 위치별로 어떤 key를 남길지 재배치하는 작업이다.

    두 번째 화면은 이행 중 가장 중요한 조건을 보여 준다. legacy key와 새 key가 동시에 살아 있으므로 한 번에 모든 경로를 끊을 필요는 없다.

    Supabase migration 가이드는 legacy key와 새 key가 동시에 동작하므로 클라이언트를 단계적으로 바꾸라고 안내한다.
    Supabase migration 가이드는 legacy key와 새 key가 동시에 동작하므로 클라이언트를 단계적으로 바꾸라고 안내한다.

    그래서 순서를 잘 짜면 무중단에 가깝게 옮길 수 있다. 반대로 이 사실을 모르고 기존 anon과 service_role을 먼저 꺼 버리면 영향 범위를 설명하기 어려워진다.

    세 번째 자료는 backend 교체 규칙이다. service_role을 쓰던 서버 경로는 secret key로 바꾸되, 브라우저에는 두지 말라는 선이 더 분명해졌다.

    Supabase는 public code의 anon을 publishable로, backend의 service_role을 secret으로 단계적으로 교체하라고 적는다.
    Supabase는 public code의 anon을 publishable로, backend의 service_role을 secret으로 단계적으로 교체하라고 적는다.

    이미 publishable·secret·service_role 분기 글이 런타임 분리를 다뤘다면, 이번 글은 그 위에 실제 migration 순서를 얹은 후속편이다.

    순서를 표로 붙여 놓으면 브라우저, 서버, Edge Functions, webhook 순서가 자연스럽게 갈린다. 특히 public code와 backend code를 섞지 않는 것이 핵심이다.

    legacy key에서 새 key로 바꿀 때 경로별 교체 순서를 정리한 표다.
    legacy key에서 새 key로 바꿀 때 경로별 교체 순서를 정리한 표다.

    RLS 기대치와 Authorization 흐름은 RLS와 Authorization 헤더 글과 함께 보면 더 빨리 맞춰진다.

    교체는 코드 한 줄 차이처럼 보이지만 실제로는 저장 위치와 rotate 단위가 바뀐다. secret key는 브라우저에서 401이 나도록 보호가 더 붙었지만, 그것이 노출 허용을 뜻하지는 않는다.

    public code와 backend code를 분리해서 key를 바꾸는 예시다.
    public code와 backend code를 분리해서 key를 바꾸는 예시다.

    특히 webhook이나 SQL 쪽은 Authorization Bearer에 service_role을 넣던 습관을 apikey 헤더 방식으로 바꾸는지 꼭 확인해야 한다.

    마지막 자료는 실제 cutover 순서다. 이 체크리스트를 쓰면 legacy key를 언제까지 병행하고 어느 시점에 끌 수 있는지 문서로 남기기 쉽다.

    publishable·secret key 전환과 legacy key 종료 타이밍을 정리한 체크리스트다.
    publishable·secret key 전환과 legacy key 종료 타이밍을 정리한 체크리스트다.

    한 번에 다 바꾸는 것보다 브라우저, 서버, webhook 순으로 끊어 가는 편이 더 안전하다.

    5. 주의사항과 리스크

    첫 번째 리스크는 secret key가 브라우저에서 401을 준다고 해서 노출돼도 괜찮다고 오해하는 것이다. 두 번째는 service_role을 secret으로 바꾸면서도 사용자 세션이 섞이는 SSR helper를 그대로 두는 것이다. 세 번째는 webhook이 아직 Authorization Bearer service_role에 기대고 있다는 사실을 마지막까지 놓치는 것이다.

    운영 전에 확인할 때는 key 종류, 실행 위치, Authorization 출처, legacy 의존 여부 네 항목을 같은 표에 두는 편이 좋다. 그래야 cutover 시점을 설명할 수 있다.

    • public code는 publishable, backend는 secret으로 고정한다.
    • legacy key 병행 기간은 명시적으로 남긴다.
    • webhook과 SQL 호출의 헤더 방식까지 migration 범위에 넣는다.

    6. 결론

    Supabase key migration은 새 publishable·secret key를 만든 뒤 브라우저, backend, webhook 순서로 경로별로 나눠 옮기는 작업이다. legacy key가 병행 동작하는 동안 경로별로 교체하고, 의존이 사라졌다는 로그를 확인한 뒤 끄는 편이 가장 안전하다.

    migration을 끝낸 다음에는 backend 전체가 아니라 서비스 경계별로 secret key를 더 잘게 나누는 일이 남는다. 그 기준과 유출 시 rotation 순서는 backend별 secret key 분리 후속 글에서 이어서 정리했다.

    • anon은 publishable로 먼저 바꾼다.
    • service_role은 secret으로 backend에서만 바꾼다.
    • legacy key 종료는 마지막 확인 단계로 둔다.

    7. 참고 링크

    1. https://supabase.com/docs/guides/getting-started/api-keys
    2. https://supabase.com/docs/guides/getting-started/migrating-to-new-api-keys
    3. https://supabase.com/docs/guides/auth/jwts
    4. https://supabase.com/docs/guides/troubleshooting/rotating-anon-service-and-jwt-secrets-1Jq6yd
Designed by Tistory.