Supabase 연결 주소는 옮겨 적는 대신 OAuth 승인으로 채운다

Supabase 대시보드에는 같은 DB로 가는 주소가 셋 있습니다. 프로젝트의 직접 주소, 공유 풀러(Supabase가 프로젝트 앞에 두는 커넥션 풀러)의 트랜잭션 모드 포트 6543, 같은 풀러의 세션 모드 포트 5432입니다. 이 가운데 OwlAI가 쓰는 주소는 세션 모드 5432 하나이고, 계정 이름 끝에는 .<프로젝트 ref>가 붙어야 하며, 서버 인증서는 시스템 신뢰 저장소에 없는 Supabase 루트 CA로 확인해야 합니다. 관리자가 주소를 옮겨 적다가 6543을 고르면 prepared statement로 SQL을 보내는 OwlAI의 PostgreSQL 실행기가 돌지 않고, 역할 이름만 적으면 풀러가 접속을 거절하며, TLS 확인을 켜면 인증서 검증이 실패합니다.
그래서 OwlAI SaaS에서는 관리자가 주소를 옮겨 적지 않습니다. Supabase로 연결을 눌러 Supabase에서 OwlAI를 승인하면, OwlAI가 Supabase Management API로 고른 프로젝트의 풀러 좌표를 읽어 등록 창을 채웁니다. 이 승인은 칸을 채우는 한 번에만 씁니다. 그래서 액세스 토큰은 한 시간만 들고 있고, 로그인 없이 돌아오는 콜백은 승인을 시작한 브라우저에서만 마칩니다. 이 글은 채우는 좌표, 승인을 짧게 쥐는 방법, 콜백을 승인한 사람에게 잇는 방법을 설명합니다. OAuth 앱이 OwlAI가 Supabase에 등록한 앱 하나라서 이 기능은 OwlAI SaaS에만 있습니다.
세 주소 가운데 맞는 것은 세션 모드 풀러의 5432다
관리자가 프로젝트를 고르면 서버는 그 프로젝트의 풀러 설정을 읽고, 풀러 호스트와 DB 이름에 세션 모드 포트 5432를 붙여 칸을 채웁니다. 세 주소를 비교하면 이렇습니다(Supabase 문서, 2026-11-03 확인).
| 주소 | OwlAI에서 |
|---|---|
| 직접 주소 | 기본이 IPv6라 IPv4만 있는 망에서는 닿지 않습니다. Supabase 문서도 IPv4 망에는 세션 모드 풀러를 권합니다 |
| 트랜잭션 모드 6543 | 서버리스·엣지 함수용이고 prepared statement를 받지 않습니다. OwlAI의 PostgreSQL 실행기는 SQL을 prepared statement로 실행합니다 |
| 세션 모드 5432 | IPv4로 닿고 prepared statement가 그대로 돕니다. 이 주소로 채웁니다 |
풀러 응답에도 포트와 풀 모드가 실려 오지만 그 값은 쓰지 않습니다. 프로젝트의 트랜잭션 모드 설정을 알려 주는 값이라 그대로 따르면 6543을 받습니다. 응답에 읽기 복제본의 풀러가 섞여 오면 주 DB의 것을 고릅니다.
계정 칸은 비워 둡니다. Supabase가 알려 주는 기본 계정 postgres.<ref>는 쓰기 권한까지 가진 관리 계정이라, AI가 조회할 계정은 관리자가 만든 읽기 전용 역할로 정하게 합니다. 관리자는 역할 이름만 넣습니다. 서버는 등록할 때 그 이름 뒤에 .<ref>를 붙입니다. 같은 ref가 이미 붙어 있으면 그대로 두고, 다른 프로젝트의 ref가 붙어 있으면 SUPABASE_USERNAME_PROJECT_MISMATCH로 거절합니다. 비밀번호는 Management API가 내주지 않으므로 관리자가 넣습니다.
TLS는 필수이고, 서버 인증서의 체인과 호스트 이름을 모두 검증합니다(libpq sslmode=verify-full). 검증 기준은 제품에 내장한 Supabase 루트 CA(2031년 4월 만료)입니다. 연결에 CA가 실리면 그 CA만 믿는 규칙은 「인증서 오류는 검증을 끄지 않고, 빠진 CA와 틀린 이름을 고쳐서 푼다」에서 다룹니다. 이 연결은 설치의 신뢰 저장소를 거치지 않으므로 사내 CA 번들을 바꿔도 검증 결과가 달라지지 않습니다.
승인으로 받은 액세스 토큰은 한 시간만 쥔다
승인을 시작하면 서버는 가져오기 세션 행을 하나 만듭니다. 승인 대기부터 프로젝트 선택까지의 상태가 이 행에 있고, 수명은 두 단계입니다.
| 단계 | 행에 있는 것 | 수명 |
|---|---|---|
| 승인 대기 | state, PKCE code verifier, 브라우저 결속 쿠키의 해시 |
10분 |
| 준비됨 | Supabase 액세스 토큰 | 1시간 |
승인 주소에는 state와 S256 code challenge가 실립니다. 인가 코드(authorization code)를 교환할 때는 서버가 verifier와 OAuth 앱의 client secret을 함께 보냅니다. 교환에 성공하면 같은 행이 준비됨 단계로 바뀌고, verifier 자리에 액세스 토큰이 들어갑니다. 리프레시 토큰은 버립니다. 액세스 토큰으로 하는 일은 조직·프로젝트 목록과 풀러 좌표를 읽는 것뿐이고, 그 일은 관리자가 화면 앞에 있는 한 시간 안에 끝납니다.
verifier와 액세스 토큰은 봉투 암호화로 저장하고, 암호화 컨텍스트에 용도·조직·행 id를 넣습니다. 그래서 암호문을 다른 행으로 복사하면 풀리지 않습니다. 풀리지 않는 행은 없는 행과 똑같이 다룹니다.
만료 시각은 DB의 now()로 쓰고 같은 시계로 비교하므로, 백엔드 복제본끼리 호스트 시계가 어긋나도 판정은 같습니다.
세션을 읽는 경로는 만료 행을 만나면 거절하면서 지웁니다. 승인만 하고 돌아오지 않은 사람의 행은 아무도 읽지 않는데, 그 토큰은 Supabase 쪽에서 아직 유효할 수 있습니다. 이런 행은 한 시간마다 도는 정리 잡이 지웁니다.
세션은 등록에 성공해도 남습니다. 같은 Supabase 계정의 다른 프로젝트를 이어서 등록할 수 있게 화면이 세션 만료 시각까지 다른 프로젝트 등록을 보여 주고, 끝내기를 누르면 세션을 바로 지웁니다.
로그인 없이 돌아오는 콜백은 승인을 시작한 브라우저에서만 마친다
Supabase에서 돌아오는 콜백은 브라우저의 최상위 GET 이동이라 OwlAI 로그인 토큰을 싣지 않습니다. 서버는 state로 행을 찾고 그 행의 조직 안에서만 읽고 씁니다. 행을 잠근 채 state를 읽고 비우므로 state 하나는 한 번만 쓰입니다.
state로는 막을 수 없는 공격이 하나 있습니다. 공격자가 자기 OwlAI 조직에서 승인을 시작하고, 그 승인 주소를 피해자에게 열게 하는 경우입니다. Supabase에 로그인해 있는 피해자가 승인하면 콜백은 정상으로 돌아오고, 피해자의 Supabase 토큰이 공격자가 시작한 세션에 들어갑니다. 공격자는 한 시간 동안 피해자의 프로젝트 목록과 풀러 주소를 읽을 수 있습니다. PKCE도 이 경우를 막지 못합니다. verifier는 서버가 들고 있고 교환도 서버가 하기 때문입니다.
그래서 승인 시작 응답이 그 브라우저에 1회용 결속 쿠키를 심고, 행에는 쿠키 값의 해시만 적습니다. 쿠키는 HttpOnly이고 SameSite=Lax입니다. Lax라서 Supabase에서 돌아오는 최상위 이동에도 쿠키가 실립니다. 콜백은 쿠키의 해시를 행과 대조하고, 쿠키가 없거나 다르면 인가 코드를 교환하지 않은 채 행을 지운 뒤 SUPABASE_IMPORT_BROWSER_MISMATCH로 끝냅니다. 공격자가 건넨 주소로 돌아온 피해자의 브라우저에는 그 쿠키가 없습니다.
데스크톱 앱에서 Supabase로 연결을 누르면 승인부터 등록까지 웹 브라우저에서 합니다. 콜백은 시스템 브라우저로 돌아오는데 결속 쿠키는 승인을 시작한 쪽에만 있기 때문입니다.
콜백은 결과를 URL 프래그먼트(# 뒤 부분)에 실어 DB 관리 화면으로 돌려보냅니다(#supabaseImport=<세션 id> 또는 #supabaseImportError=<errorCode>). 쿼리에 실으면 세션 id가 서버 접근 로그와 다음 요청의 Referer에 남는데, 프래그먼트는 브라우저가 서버로 보내지 않습니다. 화면은 첫 렌더에서 프래그먼트를 읽고 바로 주소에서 지웁니다. 예상하지 못한 예외가 나도 콜백은 SUPABASE_UNAVAILABLE을 실어 같은 화면으로 보내므로, 관리자는 빈 오류 페이지 대신 다시 시작할 버튼을 봅니다.
세션을 읽는 API(프로젝트 목록, 칸 미리 채움, 세션 삭제)는 세션을 시작한 관리자만 부를 수 있습니다. 같은 조직의 다른 관리자는 세션 id를 알아도 없는 세션과 같은 응답을 받고, 다른 조직의 행은 행 수준 보안이 가립니다.
등록할 때 Supabase에 좌표를 한 번 더 묻는다
등록 창의 등록은 손으로 채운 직접 연결과 같은 등록 API를 부릅니다. 요청에 가져오기 세션 id와 프로젝트 ref가 함께 가면, 서버는 그 세션으로 Supabase에 풀러 좌표를 다시 묻습니다. 요청의 엔진·호스트·포트가 Supabase의 답과 다르면 422 SUPABASE_IMPORT_TARGET_MISMATCH로 거절하고, TLS는 요청과 상관없이 서버가 필수 + 내장 CA로 정합니다. 그래서 연결 출처가 Supabase인 연결은 Supabase가 지금 알려 주는 서버로만 만들어집니다.
그다음 접속 대상 판정, 분당 접속 시도 상한, 실제로 붙어 테이블 목록 읽기, 한 트랜잭션 저장은 직접 연결과 똑같이 돌고, 붙지 못하면 아무것도 저장하지 않습니다. 목록에서 골라 칸을 채우고 기존 등록 API로 저장하는 짜임은 Amazon RDS 가져오기(「RDS 목록은 액세스 키 대신 60분 뒤 만료되는 역할로 읽는다」)와 같습니다.
저장된 연결의 출처는 Supabase로 고정되고 프로젝트 ref가 함께 남습니다. 출처마다 다른 규칙은 연결 출처의 정의 한 곳에서 나옵니다(「SQL 실행기가 둘이어도 판정과 결과 형식은 한 벌만 둔다」). Supabase 출처는 공개 풀러로만 붙으므로 SSH 터널을 받지 않습니다. 수정에서는 엔진·호스트·포트와 TLS를 바꿀 수 없고(DB_CONNECTION_SOURCE_LOCKED), 바꾸는 길은 다시 가져오기 하나입니다.
설계에서 고른 것
관리자가 Supabase 개인 액세스 토큰을 붙여 넣게 하는 대신 OAuth 승인을 골랐습니다. 붙여 넣는 방식은 콜백도 세션 행도 필요 없어 단순하지만, OwlAI가 그 토큰을 관리자가 지울 때까지 들고 있어야 합니다. 칸을 한 번 채우는 데 그만큼 오래 가는 권한은 지나칩니다.
리프레시 토큰을 버렸습니다. 갱신할 수 있으면 승인 한 번으로 오래 쓸 수 있지만, 이 기능은 칸을 채우면 할 일이 끝납니다. 대가로 한 시간이 지나면 다시 승인해야 하고, OwlAI 화면에서는 승인을 철회할 수 없습니다. 철회는 Supabase 계정의 승인한 앱 목록에서 합니다.
콜백을 승인을 시작한 브라우저에 묶었습니다. 남이 건넨 승인 주소로는 세션이 생기지 않는 대신, 쿠키를 지웠거나 다른 브라우저에서 콜백을 열면 승인을 처음부터 다시 해야 합니다.
드라이버에서 prepared statement를 끄고 6543을 쓰는 대신 풀러 포트를 5432로 고정했습니다. 트랜잭션 모드는 서버 연결 하나를 여러 클라이언트가 나눠 쓰므로 연결 수에 여유가 생깁니다. 하지만 OwlAI의 PostgreSQL 실행기는 모든 PostgreSQL 연결에서 SQL을 prepared statement로 실행하고, 세션 모드에서는 그 방식이 그대로 돕니다. 이미 6543으로 저장된 연결은 자동으로 옮기지 않고, 수정 잠금이 저장된 서버를 기준으로 비교하므로 그 연결의 다른 칸은 고칠 수 있습니다.
등록할 때 Supabase에 좌표를 다시 묻습니다. 화면이 채운 값을 믿으면 호출이 줄지만, 출처가 Supabase로 적힌 연결이 Supabase가 모르는 서버를 가리킬 수 있습니다. 대가로 등록할 때마다 Supabase를 두 번 더 읽고, Supabase가 응답하지 않으면 재시도 상한까지 최대 약 41초를 기다립니다.
Supabase 루트 CA를 제품에 실었습니다. 관리자가 CA 파일을 찾아 붙여 넣을 일이 없는 대신, 2031년 4월 전에 새 CA를 실은 판을 내야 합니다. 만료 1년 전부터 부팅할 때 경고가 남습니다.
외부 서비스의 콘솔에서 값을 한 번 읽어 와 칸을 채우는 기능이라면 같은 조건이 걸립니다. 토큰은 칸을 채우는 시간만큼만 쥐고, 로그인 없이 돌아오는 콜백은 state와 함께 승인을 시작한 브라우저의 쿠키로 확인합니다.
어떤 문제를 해결하나요?
DB 연결과 OwlDB의 다른 글

엔지니어링 노트 · DB 연결과 OwlDB
배스천에는 계정 대신 DB 포워딩만 여는 키 한 줄을 넣는다
운영 DB가 사설망 안에 있고 그 앞에 배스천 호스트(사설망으로 들어가는 SSH 서버)가 있으면, OwlAI는 배스천을 거치는 SSH 터널로 DB에 붙습니다.

엔지니어링 노트 · DB 연결과 OwlDB
인증서 오류는 검증을 끄지 않고, 빠진 CA와 틀린 이름을 고쳐서 푼다
기업망에 설치한 OwlAI가 클라우드 LLM이나 고객 DB에 TLS로 붙을 때, 인증서 오류가 나는 경우는 크게 둘입니다.

엔지니어링 노트 · DB 연결과 OwlDB
RDS 목록은 액세스 키 대신 60분 뒤 만료되는 역할로 읽는다
OwlAI SaaS에서 고객의 Amazon RDS·Aurora를 연결하려면 엔드포인트·포트·엔진과 함께 DB가 공개 서브넷에 있는지, 어느 VPC·보안 그룹에 속하는지 같은 망 정보가 필요합니다.
