-
[Supabase][인증] signing key rotation 뒤 verify_jwt false 함수와 signed-in 함수의 종료 기준을 어떤 checklist로 따로 남기나기타개발지식/풀스택개발 2026. 8. 21. 20:17
IT 리서치 노트
[Supabase][인증] signing key rotation 뒤 verify_jwt false 함수와 signed-in 함수의 종료 기준을 어떤 checklist로 따로 남기나
Supabase signing key rotation을 몇 번 운영하다 보면 "Verify JWT를 껐다"와 "signed-in 함수도 이제 새 키로 잘 돈다"를 같은 완료 문장으로 적기 쉽다. 하지만 2026년 8월 21일 기준 Supabase 공식 문서를 다시 보면, rotation 중 Verify JWT 잔존 설정은 앱을 깨뜨릴 수 있고, signed-in 사용자 함수는 여전히 플랫폼 JWT 검증을 기본값으로 유지해야 하며, publishable·secret key 기반 Edge Functions는 경우에 따라 verify_jwt=false와 자체 authorization 경로가 필요하다. 이 글은 rotation 뒤 verify_jwt=false 함수와 signed-in 함수의 종료 기준을 왜 같은 checklist로 닫으면 안 되는지 정리한다.
1. 개요
결론부터 말하면 Supabase signing key rotation 뒤 종료 기준은 함수 유형별로 따로 닫아야 한다. signed-in 사용자 함수는
verify_jwt=true 유지 + 사용자 JWT 통과 + auth:'user' 문맥 유지를 성공 조건으로 적고, verify_jwt=false 함수는익명 또는 apikey-header 호출 + 자체 authorization 코드 통과를 성공 조건으로 적는 편이 맞다.둘을 같은 표로 닫으면 signed-in 함수가 정상인데 verify_jwt=false health check가 깨진 상황을 rotation 전체 실패로 보고하거나, 반대로 특정 Edge Function migration 성공을 근거로 signed-in 경로까지 verify_jwt=false로 넓혀 버리는 회귀가 생긴다. rotation은 키 교체 사건이고, 함수별 종료 기준은 인증 경계 문서다.
2. 어디서 실제로 막히는가
현장에서 가장 자주 섞이는 지점은 verify_jwt=false가 필요한 함수의 존재 자체를 signed-in 함수 기본값 변경으로 오해하는 순간이다. signed-in 사용자가
supabase.functions.invoke로 호출하는 함수는 사용자의 session JWT가 Authorization 헤더로 전달되고, Supabase는 이 경로에서 verify_jwt=true를 기본으로 두라고 설명한다. 반면 health check나 publishable·secret key migration 경로처럼 사용자 JWT를 기대하지 않는 함수는 verify_jwt=false와 별도 authorization 로직이 필요할 수 있다.문제는 rotation 체크리스트가 하나일 때 생긴다. 어떤 팀은 standby key 생성, rotate 시각, revoke 대기 시간만 남긴다. 어떤 팀은 Edge Functions Verify JWT 토글만 보고 모든 함수를 verify_jwt=false로 몰아 적는다. 또 어떤 팀은 getClaims 경로가 정상이라는 이유로 signed-in 함수와 public function을 동시에 완료 처리한다. 이 세 경우 모두 함수가 기대하는 Authorization 형태를 기록하지 않아 실패 범위를 넓혀 버린다.
특히 publishable·secret key migration 문서는 Edge Functions built-in verify_jwt 체크가 legacy JWT 기반 전제와 맞물릴 수 있으므로 verify_jwt=false와 자체 authorization 경로를 쓰라고 적는다. 이것은 모든 함수에서 verify_jwt를 끄라는 뜻이 아니라, 플랫폼 JWT 검증이 맞지 않는 일부 함수의 종료 기준을 별도로 세우라는 뜻이다. signed-in 함수까지 같은 줄에 넣으면 RLS 경계와 anonymous health check 경계가 한 덩어리로 섞인다.
- 증상: signed-in 함수는 정상인데 특정 Edge Function만 rotation 뒤 401 또는 Invalid JWT를 낸다.
- 실패: verify_jwt=false migration 경로를 signed-in 함수 기본값처럼 적는다.
- 막힘: 함수별로 기대하는 Authorization 헤더 형태를 메모에 안 남긴다.
- 누락: revoke 시점을 함수 성공 시각과 분리하지 않는다.
보이는 현상 섞이면 안 되는 축 먼저 적을 값 signed-in 함수는 통과한다 verify_jwt=false 함수와 동일 종료 처리 auth:'user', user JWT kid, RLS scope publishable key 함수만 깨진다 rotation 실패와 함수 인증 코드 실패 verify_jwt=false, apikey header path, own auth check revoke 직후 일부 경로만 다시 실패한다 종료 기준과 revoke 타이밍 access token TTL, safety window, legacy secret dependency 3. 실무에서 적용하는 순서
가장 짧은 방법은 함수 inventory를 세 줄로 나누는 것이다. 첫째, signed-in 함수는 verify_jwt=true를 유지하고
auth:'user'문맥, RLS 적용, 새 kid JWT 통과를 함께 적는다. 둘째, public 또는 health check 함수는 verify_jwt=false, anonymous 허용, status code 정상 여부를 적는다. 셋째, publishable·secret key 기반 Edge Functions는 verify_jwt=false, apikey 헤더 검증, 자체 authorization 또는@supabase/server경로를 적는다.- 현재 함수가 signed-in 사용자 경로인지 auth:none 또는 apikey 경로인지 먼저 적는다.
- signed-in 함수는 verify_jwt=true와 user JWT 통과 여부를 확인한다.
- verify_jwt=false 함수는 anonymous 또는 apikey 기반 허용 로직을 확인한다.
- 각 함수의 첫 정상 시각을 따로 적고 revoke 시각과 분리한다.
- legacy secret revoke는 TTL+safety window 뒤에만 진행한다.
- 함수 목록을 분리하고 역할을 기록하고 헤더 값을 확인한다.
- signed-in 경로를 유지하고 JWT를 검증하고 RLS 범위를 점검한다.
- verify_jwt=false 경로를 호출하고 apikey 헤더를 비교하고 허용 코드를 검토한다.
- 첫 성공 시각을 저장하고 revoke 시각을 분리하고 fallback 경로를 남긴다.
- inventory를 갱신하고 키를 교체하고 legacy secret 의존을 제거한다.
- 오류 응답을 추적하고 함수별 결과를 비교하고 회귀 여부를 복원한다.
운영 메모에는 실행한 명령, 확인한 설정, 조회한 로그, 받은 응답, 바꾼 권한, 남긴 파일, 저장한 결과를 같이 적어 두는 편이 좋다. 그래야 signed-in 경로와 verify_jwt=false 경로를 다시 실행하고 다시 확인할 때도 같은 순서로 검증할 수 있다.
여기서 중요한 것은 verify_jwt=false가 곧 "보안이 약한 함수"를 뜻하지 않는다는 점이다. 보안은 verify_jwt 토글이 아니라 어떤 헤더를 어떤 코드 경로에서 확인하는지에 달려 있다. signed-in 함수는 플랫폼이 JWT를 먼저 거르는 편이 맞고, publishable·secret key 기반 함수는 apikey 헤더와 내부 authorization 경로를 코드로 닫는 편이 맞다. 종료 기준은 이 구조에 따라 달라진다.
또 rotation 뒤 함수를 닫을 때는 revoke 완료와 섞지 않는 편이 좋다. 함수가 정상이라는 사실은 그 함수가 기대하는 새 경로가 통과했다는 뜻이지, 이전 legacy JWT secret 의존이 전부 사라졌다는 뜻은 아니다. signed-in 함수, verify_jwt=false 함수, revoke 대기 시간을 세 줄로 유지하면 보고가 훨씬 짧아진다.
4. 공식 문서와 예시 화면으로 확인하기
첫 공식 자료는 signing keys 문서의 경고다. Supabase는 rotation을 진행할 때 Edge Functions의 Verify JWT 설정이 남아 있으면 앱이 깨질 수 있다고 직접 적는다.
이 문장이 중요한 이유는 모든 함수를 같은 종료 기준으로 닫으면 안 된다는 점을 드러내기 때문이다. signed-in 사용자 JWT를 플랫폼이 검증해야 하는 함수와, verify_jwt를 끄고 앱 코드로 직접 허용 여부를 판단해야 하는 함수는 rotation 메모가 서로 다르다.
두 번째 자료는 signed-in 사용자 경로다. Supabase는 클라이언트에서 호출되는 signed-in 함수는 사용자의 session JWT를 Authorization 헤더로 보내며, 이 경우 verify_jwt를 기본값으로 유지하라고 설명한다.
즉 rotation 뒤에도 signed-in 함수의 종료 기준은 '플랫폼 검증이 통과하고 사용자 RLS 문맥이 유지되는가'다. 이 함수까지 한꺼번에 verify_jwt=false로 넘기면 회전 대응이 아니라 인증 경계 회귀가 된다.
세 번째 공식 자료는 publishable·secret API keys migration 문서다. 여기서는 Edge Functions에서 built-in verify_jwt 체크가 legacy JWT 기반 전제에 묶여 있으므로 특정 함수는 verify_jwt=false로 두고 자체 authorization 로직을 넣으라고 안내한다.
이 경로는 signed-in 사용자 함수를 대체하는 것이 아니라 auth:none 또는 apikey-header 기반 함수의 별도 종료 기준을 만든다. rotation 대응 문서에서 두 함수를 한 줄에 적으면 어떤 함수가 플랫폼 JWT를 기대하고 어떤 함수가 자체 검증을 기대하는지 구분이 사라진다.
실무에서는 종료 기준 표가 가장 먼저 필요하다. verify_jwt=false 함수와 signed-in 함수는 둘 다 'JWT를 다룬다'고 보이지만 성공 판정 지점이 다르다.
이미 rotation 전 Verify JWT와 getClaims 분기 글과 verifier cache window 글로 전환 축을 잡았다면, 이번 표는 회전 뒤 종료 기준만 따로 문서화하는 후속편이다.
마지막 자료는 회전 뒤 메모 예시다. 함수 유형별 종료 기준을 따로 적어 두지 않으면 signed-in 경로가 정상인데 verify_jwt=false 함수만 깨졌을 때도 rotation 전체가 실패한 것처럼 보고하게 된다.
이 정도만 남겨도 어떤 함수가 플랫폼 JWT 검증을 기대하는지, 어떤 함수가 auth:none 또는 apikey 인증을 기대하는지 회고가 짧아진다. 긴급 revoke 대응은 cache purge와 session invalidation 글과 별도 축으로 두는 편이 맞다.
5. 주의사항과 리스크
첫 번째 리스크는 verify_jwt=false migration 예시를 모든 함수의 새 기본값으로 오해하는 것이다. 두 번째 리스크는 signed-in 함수가 정상이라는 이유로 public/auth:none 함수도 자동으로 정상일 것이라 가정하는 것이다. 세 번째 리스크는 함수별 종료 기준을 적지 않고 revoke만 서두르는 것이다.
rotation 전에 확인할 것은 키 자체보다 함수 유형 inventory다. signed-in 함수, public function, publishable·secret key function을 나누지 않으면 실패 보고가 과장되거나 반대로 누락된다. 특히 RLS 문맥을 기대하는 signed-in 함수를 verify_jwt=false로 넓히는 회귀는 훨씬 큰 사고를 만든다.
- verify_jwt=false는 일부 함수의 별도 인증 경로이지 전체 기본값이 아니다.
- signed-in 함수의 종료 기준은 user JWT와 RLS 문맥 유지다.
- revoke 시각은 함수 성공 시각과 따로 적는다.
6. 결론
Supabase signing key rotation 뒤에는 "회전이 끝났다"보다 "어떤 함수가 어떤 기준으로 정상인가"를 따로 적는 편이 맞다. signed-in 함수는 verify_jwt=true와 user JWT 문맥으로 닫고, verify_jwt=false 함수는 anonymous 또는 apikey 기반 authorization 코드로 닫아야 rotation 대응과 인증 설계 회귀를 구분할 수 있다.
같은 verify_jwt=false라도 caller를 더 좁혀 적어야 한다면 바로 이어서 anonymous health check와 apikey-only route caller inventory 글을 보면 된다. 그 글은 이번 종료 기준 문서를 바탕으로 no-credential probe, apikey-only route, anonymous user JWT 경로를 실제 inventory 줄로 다시 나누는 후속편이다.
- signed-in 함수와 verify_jwt=false 함수의 종료 기준을 같은 줄에 쓰지 않는다.
- 함수별 첫 정상 시각과 revoke 시각을 분리한다.
- rotation 메모는 키 교체와 인증 경계를 함께 보되 한 표로 뭉개지 않는다.
7. 참고 링크
'기타개발지식 > 풀스택개발' 카테고리의 다른 글