https://supabase.com/docs/guides/functions/schedule-functions
https://supabase.com/docs/guides/database/vault
수퍼베이스에서 기본 제공하는 스케줄러를 사용해서 예약 30분 전 슬랙 알림을 보내는 기능을 구현했다.
service_role_key를 헤더에 하드코딩하는 미친짓을 해보기도 하고
anon, publishable 키들을 사용해 보기도 하고
cron secret이라는 임시키를 만들어서 헤더에도 넣어보고 바디에도 넣어보았지만
401 에러만 뱉고 로그를 찍지도 않는 상황이 반복됐다.
등의 조언을 들으면서 로그를 찍어보다가,
일단 edge function이 JWT 토큰 형식으로 Authorization 헤더 검사를 하는 것 같다는 결론에 도달했다.
공식 문서에서도 anon 키를 쓰라고 되어 있어서,
쓸데없이 하던 거 다 갖다 버리고 공식 문서대로 하되 anon 키를 vault에 저장해두고 불러 쓰는 식으로 바꾸니까 됐다.
-- **실행한 sql문 정리**
-- vault에 anon key 넣기
SELECT vault.create_secret(
'eyJ...', -- Dashboard의 실제 anon key (JWT 형식)
'publishable_key'
);
-- pg_cron job 업데이트 (Authorization 헤더 포함)
SELECT cron.alter_job(
(SELECT jobid FROM cron.job WHERE command LIKE '%check-reminders%' LIMIT 1),
command := $$
SELECT
net.http_post(
url := 'https://프로젝트.supabase.co/functions/v1/check-reminders',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'Authorization', 'Bearer ' || (
SELECT decrypted_secret
FROM vault.decrypted_secrets
WHERE name = 'publishable_key'
)
),
body := '{}'::jsonb
) AS request_id;
$$
);
-- 업데이트된 job 확인 (Authorization과 Bearer 포함 여부 확인용)
SELECT command
FROM cron.job
WHERE command LIKE '%check-reminders%';
-- 수동으로 함수 호출 테스트
SELECT
net.http_post(
url := 'https://프로젝트.supabase.co/functions/v1/check-reminders',
headers := jsonb_build_object(
'Content-Type', 'application/json',
'Authorization', 'Bearer ' || (
SELECT decrypted_secret
FROM vault.decrypted_secrets
WHERE name = 'publishable_key'
)
),
body := '{}'::jsonb
) AS request_id;
해결된 이유는... supabase key에 대한 깃허브 토론과 공식 문서에서 실마리를 찾을 수 있었다.
이런거 많이 봐야지 개발실력이 는다는 유튜브 영상을 봤는데,
트러블 슈팅할 때는 무조건 이것들부터 보는 습관을 길러야 할 것 같다..
API key가 있는 페이지만 봐서는 절대 이해 불가
https://supabase.com/docs/guides/api/api-keys
https://github.com/orgs/supabase/discussions/29260
최근 Supabase는 보안 강화를 위해 anon / service_role 키 대신
publishable / secret 키 체계를 새로 도입했는데,
이 새 키들(욕 아님)은 JWT 형식이 아니다.
그래서 Authorization 헤더에 Bearer sb_publishable_... 형태로 넣으면
Edge Function이 “JWT가 아님”이라며 무조건 401을 반환한다.
반면에 기존의 anon 키는 여전히 JWT 기반이어서
기본적으로 JWT 검증이 걸려 있는 Edge Function에서도 정상적으로 인증이 통과된다.
즉, publishable 키로는 안 되고 anon 키로는 되는 이유가 바로 이 구조 차이 때문이다.

edge function에 자동으로 들어가있는 anon key는 로그 찍어보면 publishable형식이다.
그래서 DB에서 edge function 호출할 때 publishable 키를 써야 할 줄 알았지만?
요청 헤더에 JWT(= 레거시 anon 키)를 보내줘야 통과되는 구조라는 거임... 껄껄
기본 설정 그대로라면 Authorization 헤더 + JWT가 있어야 하는데,
헤더 검증을 안하게 하려면 배포 시 --no-verify-jwt 옵션을 사용했거나,
함수 설정에서JWT 검증 해 상태여야 한다고 한다.
cli 가 익숙하지 않아서 edge funtion을 수동으로 배포한 사람은...
배포한 함수 페이지 -> details -> funciton configuration에서 끄면 되는 것 같다.
사이트가 많이 개편되어서인지 제대로 경로를 적어놓은 곳이 별로 없다.
AI는 anon key만 쓰거나 jwt 인증을 아예 꺼버리면 보안 쪽 문제가 있으니
cron_secret 같은 임시 키를 만들어서 요청 헤더나 body에 같이 넣고 Edge Function에서 직접 비교해서
외부 무단 접근 막으라는데 일단은 생략..
추후 변경
결국 jwt 검증 아예 끄고 랜덤값 생성한거 DB엔 valut로, edge funtion엔 환경변수로 넣음
헤더에서의 검증에서는 이 값을 사용하는걸로 바꿨음