~/web-app-dev.md
WEB_APP_DEV

Self-hosted Supabase Auth URL은 환경변수가 아니라 라우팅 계약이다

10분 읽기·2026.07.09·출처 4·00
오늘의 질문

셀프호스팅 Supabase에서 로그인 callback이 한 글자 경로 차이로 깨진다면, 어디를 테스트해야 할까?

web-app-dev.md
Auth URL contract

Self-hosted Supabase Auth URL은 환경변수가 아니라 라우팅 계약이다

1. 왜 지금 봐야 하나

Supabase가 2026년 7월 6일 주간에 셀프호스팅 Docker 설정의 기본값을 바꾸고 있다. 핵심은 작아 보인다. API_EXTERNAL_URL 기본값이 예전의 http://localhost:8000에서 http://localhost:8000/auth/v1처럼 Auth 경로 prefix를 포함하는 값으로 바뀐다.

하지만 이 변경은 단순한 문자열 수정이 아니다. Supabase changelog는 이 변경이 platform, CLI, self-hosted 동작을 맞추기 위한 것이며, custom OAuth provider callback이 기본값만으로 맞게 생성되도록 하려는 것이라고 설명한다. PR #47640도 GoTrue가 redirect URI를 ExternalURL + "/callback" 형태로 만든다고 적고 있다. 그러면 base URL이 https://example.com/auth/v1일 때 최종 callback은 https://example.com/auth/v1/callback이 된다.

웹앱 개발자에게 중요한 질문은 이것이다.

환경변수 하나가 바뀌었을 때, 내 로그인·SSO·gateway·문서·테스트는 같은 URL 계약을 보고 있는가?

오늘의 핵심 문장: Auth URL은 설정값이 아니라 외부 IdP, API gateway, Auth 서버, 프론트엔드가 공유하는 라우팅 계약이다.

2. 핵심 개념

셀프호스팅 Supabase에는 URL이 여러 개 나온다. 비슷해 보이지만 역할이 다르다.

  • SUPABASE_PUBLIC_URL: Dashboard, API, Storage 등 Supabase 인스턴스의 공개 base URL에 가깝다.
  • API_EXTERNAL_URL: Auth가 callback URL을 만들 때 사용하는 외부 Auth URL이다.
  • SITE_URL: 사용자가 로그인 후 돌아갈 내 애플리케이션 URL이다.
  • gateway route: Kong 또는 Envoy가 외부 경로를 내부 서비스로 넘기는 규칙이다.

문제는 Auth가 “내 callback URL은 어디인가?”를 계산해야 할 때 생긴다. changelog와 PR에 따르면 custom OAuth provider callback은 API_EXTERNAL_URL + /callback 규칙으로 만들어진다. 따라서 API_EXTERNAL_URL=https://example.com이면 Auth가 계산한 값은 https://example.com/callback이 되지만, 실제 gateway의 Auth callback endpoint는 보통 /auth/v1/callback 아래에 있다. 그래서 새 기본값은 base URL 자체에 /auth/v1을 포함하게 만든다.

SAML도 같은 원리다. 기존 bare path였던 /sso/saml/* 공개 endpoint가 /auth/v1/sso/saml/*로 이동한다. PR 설명에 따르면 public route에는 /auth/v1이 붙지만, 내부 upstream rewrite는 GoTrue의 실제 unprefixed route를 향한다. 즉 외부 계약과 내부 구현 경로가 분리된다.

이 구조를 외우기보다 이렇게 이해하면 쉽다.

zsh — 생존확인.sh
브라우저/IdP가 보는 URL:   https://example.com/auth/v1/callback
Auth가 조립하는 공식:      API_EXTERNAL_URL + /callback
API_EXTERNAL_URL 값:       https://example.com/auth/v1
Gateway가 맡는 일:         /auth/v1/... 를 내부 Auth 서비스 경로로 전달/rewriting
내 앱 SITE_URL:            https://app.example.com

3. 최신 이슈와 연결

이번 변경에서 Supabase가 명시한 영향 범위는 비교적 분명하다.

영향을 받는 경우:

  1. ./docker 디렉터리 기반 self-hosted Supabase를 사용하고 master 또는 최신 self-hosted Docker 설정을 가져온다.
  2. .env에서 API_EXTERNAL_URLhttps://my-domain.com처럼 직접 override했다.
  3. custom OAuth provider redirect URI를 compose 파일에서 직접 만졌다.
  4. SAML SSO를 사용하고 IdP가 /sso/saml/* endpoint를 바라보고 있다.

상대적으로 영향이 적은 경우도 있다. Supabase platform만 쓰는 사용자는 영향이 없다. Google/GitHub 같은 OAuth provider에 이미 최종 callback URL을 .../auth/v1/callback으로 등록해 둔 경우도 보통 provider 콘솔 재등록이 필요하지 않다고 changelog는 설명한다. 최종 callback URL은 그대로이기 때문이다.

그러나 “최종 URL은 그대로”라는 말이 “점검하지 않아도 된다”는 뜻은 아니다. compose override나 SAML IdP 설정, proxy 설정을 직접 만진 팀은 쉽게 이중 prefix를 만들 수 있다.

zsh — 생존확인.sh
좋은 조합:
API_EXTERNAL_URL=https://example.com/auth/v1
REDIRECT_URI=${API_EXTERNAL_URL}/callback
=> https://example.com/auth/v1/callback

위험한 조합:
API_EXTERNAL_URL=https://example.com/auth/v1
REDIRECT_URI=${API_EXTERNAL_URL}/auth/v1/callback
=> https://example.com/auth/v1/auth/v1/callback

이 버그는 TypeScript 타입으로 잡히지 않는다. 문자열은 모두 유효하기 때문이다. 실제 callback round-trip 테스트나 gateway route 테스트가 필요하다.

4. 개발자 관점 해석

작은 팀이 Supabase를 셀프호스팅할 때 자주 하는 실수는 “문서에 나온 URL을 한 곳에만 넣으면 된다”고 생각하는 것이다. 하지만 인증 URL은 최소 네 주체가 동시에 맞아야 한다.

첫째, Auth 서버의 자기 인식이다. GoTrue는 API_EXTERNAL_URL을 보고 외부에 노출될 callback URL과 issuer 관련 값을 만든다. 이 값이 틀리면 Auth 서버는 스스로 잘못된 링크를 만들어낸다.

둘째, API gateway의 공개 경로다. Kong/Envoy는 /auth/v1/*를 Auth로 전달한다. SAML의 경우 PR #47640은 public path를 /auth/v1/sso/saml/*로 옮기면서 내부 rewrite는 기존 unprefixed route로 유지했다고 설명한다. gateway는 단순 reverse proxy가 아니라 외부 API 모양을 정의하는 계층이다.

셋째, 외부 IdP의 등록값이다. OAuth provider의 callback URL, SAML IdP의 ACS URL과 metadata URL은 사람이 콘솔에서 설정하는 경우가 많다. 코드 리뷰만으로는 drift를 발견하기 어렵다.

넷째, 프론트엔드 앱 URL과 Auth URL의 분리다. Supabase docs와 PR 설명은 SITE_URL이 Supabase 인스턴스가 아니라 사용자 애플리케이션 URL임을 분명히 한다. SITE_URLAPI_EXTERNAL_URL을 같은 값으로 복사해두면 로그인 이후 redirect, email link, OAuth callback의 책임이 섞인다.

AI 코딩 에이전트 시대에는 이 문제가 더 중요하다. 에이전트가 .env.example을 업데이트하면서 /auth/v1을 덧붙이고, 다른 파일에서는 예전 placeholder를 그대로 두면 “대부분 맞아 보이는” 설정이 된다. 그래서 설정 변경 PR에는 코드 diff뿐 아니라 URL truth table이 필요하다.

5. 내 프로젝트에 적용할 체크포인트

셀프호스팅 Supabase를 쓰거나, 비슷한 Auth proxy 구조를 운영한다면 오늘 10분 동안 아래 표를 채워보자.

항목기대값확인 위치
Supabase public URLhttps://<supabase-domain>.env, proxy, docs
Auth external URLhttps://<supabase-domain>/auth/v1API_EXTERNAL_URL
OAuth callback${API_EXTERNAL_URL}/callback/auth/v1/callbackprovider console, compose
SAML ACS/auth/v1/sso/saml/acsIdP 설정, gateway route
SAML metadata/auth/v1/sso/saml/metadataIdP 설정, docs
App site URLhttps://<your-app-domain>SITE_URL, redirect allow list
Gateway route/auth/v1/*가 Auth로 전달됨Kong/Envoy config

특히 다음 냄새를 찾으면 좋다.

  • API_EXTERNAL_URL/auth/v1이 없는데 custom OAuth를 쓰고 있다.
  • REDIRECT_URI/auth/v1/auth/v1이 생길 수 있는 조합이 있다.
  • SAML IdP가 아직 /sso/saml/acs를 바라본다.
  • SITE_URL에 Supabase 인스턴스 URL을 넣었다.
  • 문서, .env.example, 실제 운영 secret, gateway config가 서로 다른 URL을 말한다.
  • 로그인 테스트는 Google/GitHub만 있고 custom OAuth 또는 SAML round-trip 테스트가 없다.

6. 오늘 10분 액션

  1. .env, compose override, secret manager에서 API_EXTERNAL_URL 값을 찾는다.
  2. 값이 https://.../auth/v1 형태인지 확인한다.
  3. OAuth redirect placeholder가 ${API_EXTERNAL_URL}/callback인지 확인한다. ${API_EXTERNAL_URL}/auth/v1/callback이면 이중 prefix 위험이 있다.
  4. SAML을 쓰면 IdP의 ACS와 metadata URL을 /auth/v1/sso/saml/*로 비교한다.
  5. SITE_URL이 Supabase가 아니라 내 웹앱 URL인지 확인한다.
  6. 로그인 smoke test를 하나 만든다. 최소한 “Auth가 생성한 provider URL에 최종 callback이 /auth/v1/callback으로 들어가는지”를 로그 또는 테스트 fixture로 남긴다.
  7. AI 코딩 에이전트에게 설정 변경을 맡겼다면, “모든 URL 환경변수와 callback 결과값을 표로 출력하라”고 요구한다.

작은 팀이라면 e2e까지 만들지 않아도 된다. 아래처럼 URL 계산 테스트 하나만 있어도 regression을 줄일 수 있다.

zsh — 생존확인.sh
const apiExternalUrl = 'https://example.com/auth/v1'
const callback = new URL(`${apiExternalUrl}/callback`)

expect(callback.pathname).toBe('/auth/v1/callback')
expect(callback.pathname).not.toContain('/auth/v1/auth/v1')

중요한 것은 “문자열을 맞췄다”가 아니라 Auth 서버, gateway, IdP, 앱이 같은 계약을 공유한다는 사실을 검증하는 것이다.

7. 더 볼 자료

중복 회피 메모

로컬 content/generated와 Supabase 최근 web_app_dev 글을 확인했다. 최근 글은 Hono 서버리스 어댑터 입력 계약, Turbopack 빌드 캐시, Next.js 인증/streaming 경계, Server Actions POST 계약, Supabase OAuth/RLS 위임 권한, Supabase 브랜칭, Supabase 패스키·Realtime·RLS 테스트를 다뤘다. 이번 글은 Supabase Auth를 다시 언급하지만 OAuth 2.1 scope/RLS 위임 설계나 패스키/RLS 정책 작성이 아니라, 2026년 7월 self-hosted Docker의 API_EXTERNAL_URL /auth/v1 기본값 변경을 계기로 Auth URL, gateway route, OAuth callback, SAML endpoint가 공유하는 라우팅 계약을 점검하는 관점에 집중한다.

오늘 10분 액션+5%

댓글 0

최신순 ▾
한 줄 남기기

혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)