Custom OIDC는 로그인 버튼이 아니라 신원 경계 계약이다
외부 IdP를 하나 더 붙이면 인증 문제가 끝날까, 아니면 우리 서비스의 신원 경계가 더 복잡해질까?
Custom OIDC는 로그인 버튼이 아니라 신원 경계 계약이다
- 카테고리: web_app_dev
- 예상 읽기 시간: 10분
- 오늘의 질문: 외부 IdP를 하나 더 붙이면 인증 문제가 끝날까, 아니면 우리 서비스의 신원 경계가 더 복잡해질까?
- 핵심 출처:
- Custom OIDC Providers for Supabase Auth - 게시일: 2026-04-08, 확인일: 2026-07-29
- Custom OAuth/OIDC Providers | Supabase Docs - 문서, 확인일: 2026-07-29
- Custom Identity Providers | Supabase Features - 기능 문서, 확인일: 2026-07-29
- Supabase Auth: Identity Linking - 문서, 확인일: 2026-07-29
- OpenID Connect Discovery 1.0 - 2023-12-15, 확인일: 2026-07-29
- RFC 7636: Proof Key for Code Exchange - 2015-09, 확인일: 2026-07-29
1. 왜 지금 봐야 하나
Supabase Auth의 Custom OAuth/OIDC Providers는 “Google, GitHub 말고도 아무 로그인 제공자나 붙일 수 있다”는 기능처럼 보인다. 하지만 웹앱 개발자 관점에서 더 중요한 변화는 로그인 제공자 목록이 아니라 신원 경계(identity boundary)를 제품 코드 바깥의 표준 계약으로 다루게 됐다는 점이다.
Supabase 블로그와 문서에 따르면 Custom Provider는 두 가지 방식으로 외부 IdP를 연결한다.
- OIDC provider:
issuerURL을 주면 discovery document, endpoint, JWKS를 자동으로 해석한다. - OAuth2 provider: OIDC discovery가 없는 제공자에 대해 authorization, token, userinfo endpoint를 수동으로 지정한다.
설정이 끝나면 클라이언트에서는 기존 OAuth 흐름과 같은 방식으로 signInWithOAuth({ provider: 'custom:my-provider' })를 호출한다. 기능 문서는 “같은 클라이언트 라이브러리, 같은 sign-in flow, 같은 RLS enforcement”를 강조한다.
여기서 조심해야 할 점이 있다. “같은 sign-in flow”라는 말은 우리 제품의 권한 설계가 자동으로 안전해진다는 뜻이 아니다. 오히려 외부 IdP가 늘어날수록 개발자가 확인해야 할 계약이 늘어난다.
- 이 provider의 issuer는 정말 우리가 믿는 조직인가?
- ID token의 audience는 어떤 client id를 가리켜야 하나?
- web, iOS, Android의 client id가 다를 때 어떤 audience를 받아들일 것인가?
- email이 없는 provider를 허용하면 계정 식별과 고객지원은 어떻게 할 것인가?
- 기존 계정과 새 provider identity를 자동 또는 수동으로 연결할 때 pre-account takeover 위험은 없는가?
- RLS 정책은
auth.uid()만 보면 충분한가, 아니면 provider/assurance level/조직 소속까지 봐야 하는가?
오늘의 핵심 문장: Custom OIDC 도입은 로그인 버튼 추가가 아니라 “외부 신원을 우리 서비스의 user id와 권한 모델로 번역하는 계약”을 정하는 일이다.
2. 핵심 개념
OIDC를 아주 짧게 말하면 OAuth 2.0 위에 얹힌 “로그인용 신원 레이어”다. OAuth만으로는 “이 사용자가 누구인가”보다 “어떤 권한으로 어떤 API에 접근할 수 있는가”가 중심이다. OIDC는 여기에 ID token, issuer, subject, discovery, JWKS 같은 신원 검증 요소를 추가한다.
Custom OIDC에서 가장 중요한 단어는 issuer다. Supabase 문서에 따르면 OIDC provider를 만들 때 issuer URL을 제공하면 Supabase Auth는 다음 경로에서 discovery document를 가져온다.
{issuer}/.well-known/openid-configuration
OpenID Connect Discovery 1.0 스펙도 RP가 OP configuration metadata를 가져와 endpoint와 암호학적 메타데이터를 확인하는 과정을 정의한다. 즉 issuer는 단순 문자열이 아니라 다음 사실을 묶는 루트 신뢰점이다.
- authorization endpoint는 어디인가?
- token endpoint는 어디인가?
- userinfo endpoint는 어디인가?
- ID token 서명 검증에 사용할 JWKS는 어디인가?
- 이 토큰을 발급한 주체는 누구인가?
Supabase 문서가 말하는 자동 동작도 이 계약 위에 있다.
- OIDC discovery document를 가져온다.
openidscope는 항상 포함된다.- ID token은 discovery document의
jwks_uri에서 가져온 JWKS로 검증된다. - provider identifier는
custom:prefix를 사용한다.
여기서 custom: prefix도 작지만 중요한 설계다. custom:github-enterprise처럼 built-in provider와 custom provider를 구분하면 코드와 로그에서 “이 신원은 우리가 직접 설정한 외부 IdP에서 왔다”는 사실이 드러난다.
PKCE도 핵심이다. Supabase 블로그와 기능 문서는 custom provider에 PKCE가 기본으로 켜진다고 설명한다. RFC 7636은 PKCE가 authorization code interception attack을 완화하기 위해 code verifier와 code challenge를 사용하는 방식이라고 설명한다. 특히 모바일 앱처럼 client secret을 안전하게 숨기기 어려운 환경에서 중요하다.
정리하면 Custom OIDC의 기본 구성요소는 다음 네 가지다.
| 요소 | 개발자가 정해야 하는 계약 |
|---|---|
| issuer | 어떤 외부 IdP를 신뢰할 것인가 |
| client id / acceptable client ids | 어떤 앱·플랫폼 audience를 허용할 것인가 |
| scopes / claims | 어떤 신원 정보를 받을 것인가 |
| identity linking | 외부 identity를 기존 Supabase user와 어떻게 연결할 것인가 |
3. 최신 이슈와 연결
Supabase는 2026년 4월 Custom OIDC Providers를 발표하면서 GitHub Enterprise, regional provider, SAML-to-OIDC bridge, self-hosted GitLab 같은 사례를 들었다. 2026년 7월 현재 기능 페이지는 이 기능을 General Availability로 표시하고, self-hosted에서도 사용할 수 있다고 안내한다. 문서는 Free plan에서 custom provider 3개, Pro 이상에서 unlimited provider를 언급한다.
이 변화가 작은 웹앱 팀에게 중요한 이유는 명확하다. AI 시대의 제품은 생각보다 빨리 “개인용 앱”에서 “조직·팀·고객사별 로그인 요구”를 받는다.
예를 들어 처음에는 GitHub 로그인만 있던 개발자 도구가 어느 날 이런 요구를 받는다.
- 고객사 A는 GitHub Enterprise Server만 허용한다.
- 고객사 B는 사내 SAML IdP를 OIDC bridge로 노출한다.
- 특정 지역은 regional identity provider를 써야 한다.
- 모바일 앱은 web과 다른 client id를 가진다.
- 어떤 provider는 email claim을 안정적으로 주지 않는다.
예전에는 이런 요구가 오면 “Supabase Auth가 built-in으로 지원하지 않으니 별도 auth 서버를 둬야 하나?”라는 고민이 생겼다. Custom OAuth/OIDC는 이 경계를 Supabase Auth 안으로 가져온다. 하지만 동시에 앱 개발자는 auth를 더 이상 “로그인 화면”으로만 볼 수 없다. 인증은 다음 데이터 흐름 전체에 영향을 준다.
외부 IdP identity
-> Supabase Auth user
-> JWT claims
-> PostgREST / Edge Function request
-> PostgreSQL RLS policy
-> 제품 내부 organization / role / entitlement
특히 Supabase를 쓰는 팀은 RLS를 많이 사용한다. 문서와 기능 설명이 “same RLS enforcement”를 말할 때, 이는 Supabase Auth가 만든 user/session을 통해 기존 RLS 경로로 들어온다는 뜻에 가깝다. 하지만 RLS 정책의 내용은 여전히 우리가 써야 한다.
auth.uid() = user_id만으로 충분한 개인용 앱도 있다. 반대로 B2B SaaS에서는 organization_members, roles, tenant_id, mfa_level, provider_type 같은 조건이 필요할 수 있다. Custom OIDC를 붙이는 순간 “로그인 성공”과 “데이터 접근 허용” 사이에 더 명시적인 권한 모델이 필요해진다.
4. 개발자 관점 해석
Custom OIDC를 도입할 때 가장 흔한 오해는 “표준을 쓰니까 안전하다”이다. 표준은 필요한 부품과 검증 절차를 제공하지만, 우리 서비스의 정책을 대신 정해주지는 않는다.
첫째, provider 설정은 코드가 아니어도 배포물이다. Dashboard에서 만든 provider, Admin API로 만든 provider, callback URL, client secret rotation, enabled state는 모두 프로덕션 동작을 바꾼다. 인프라 설정처럼 변경 기록과 리뷰가 필요하다.
둘째, acceptable_client_ids는 편의 옵션이 아니라 audience 검증 범위다. web, iOS, Android client id를 모두 허용할 수 있다는 것은 멀티 플랫폼 UX에는 좋다. 하지만 잘못 넓히면 다른 앱용 token을 우리 앱이 받아들이는 문제가 생긴다. “같은 회사 앱이면 다 허용”이 아니라 “이 Supabase project가 받아들일 수 있는 audience만 허용”해야 한다.
셋째, email_optional은 가입 전환율 옵션이 아니라 계정 식별 모델 변경이다. email이 없거나 검증되지 않은 provider를 허용하면 사용자 검색, 고객지원, 계정 복구, 중복 계정 병합, 초대 링크 흐름이 달라진다. email을 primary key처럼 쓰던 제품은 여기서 깨진다.
넷째, identity linking은 UX 기능이면서 보안 경계다. Supabase Identity Linking 문서는 자동 linking이 같은 email을 가진 identity를 하나의 user로 연결한다고 설명한다. 동시에 unverified email을 이용한 pre-account takeover 위험 때문에 unconfirmed identity를 제거하는 보안 동작도 설명한다. 수동 linking은 로그인한 사용자가 다른 OAuth identity를 연결하는 beta 기능이며, SAML SSO 사용자는 보안상 linking target이 아니라고 안내한다.
다섯째, RLS는 provider 수가 늘수록 테스트가 필요하다. 최근 RLS 테스트 글에서 다룬 것처럼 정책은 말로 설명하는 것이 아니라 identity × table × command × row 조합으로 증명해야 한다. Custom OIDC를 붙였다면 최소한 다음 행렬을 만든다.
provider: built-in github / custom:github-enterprise / custom:regional-idp
user state: new user / existing linked user / organization member / removed member
command: select / insert / update / delete
row: own row / same org row / other org row / admin-only row
5. 내 프로젝트에 적용할 체크포인트
-
Provider inventory를 만든다
- built-in provider와 custom provider를 한 표에 모은다.
- identifier, issuer, client id, callback URL, scopes, owner, rotation 주기를 기록한다.
-
Issuer와 discovery를 검증한다
- issuer가 HTTPS인지 확인한다.
/.well-known/openid-configuration에서 authorization/token/userinfo/JWKS endpoint가 기대한 도메인인지 확인한다.- staging과 production issuer가 섞이지 않게 한다.
-
Audience 범위를 좁힌다
- web, iOS, Android client id를 무조건 한 provider에 몰아넣지 않는다.
acceptable_client_ids를 쓰는 경우 “왜 이 audience를 허용하는가”를 기록한다.
-
Email 의존성을 제거하거나 명시한다
- DB에서 email을 user identity key로 쓰고 있다면
auth.users.id또는 별도 profile id 중심으로 바꾼다. email_optional을 켤 때 고객지원, 초대, 알림 정책을 다시 본다.
- DB에서 email을 user identity key로 쓰고 있다면
-
Identity linking 정책을 제품 언어로 쓴다
- 같은 email이면 자동 병합할 것인가?
- 사용자가 직접 연결할 수 있는 provider는 무엇인가?
- 조직 SSO 계정과 개인 계정은 연결 가능한가?
- 계정 탈퇴·분리·복구 때 linked identity는 어떻게 처리되는가?
-
RLS 정책을 provider 추가 후 다시 테스트한다
- 새 provider로 로그인한 user가 기존 RLS를 통과하는지 확인한다.
- 단순 로그인 성공이 아니라 select/insert/update/delete 각각을 테스트한다.
- 조직에서 제거된 사용자가 custom provider로 재로그인했을 때 접근이 막히는지 확인한다.
-
Admin API 호출 위치를 제한한다
- custom provider 생성·수정 API는 secret key가 필요한 관리 작업이다.
- 클라이언트 앱, 모바일 앱, 브라우저 번들에 secret key가 들어가면 안 된다.
- provider 변경은 내부 admin tool, migration script, CI 승인 흐름 중 하나로 제한한다.
6. 오늘 10분 액션
오늘은 코드를 많이 쓰기보다 “신원 경계 지도”를 그려보자.
- 현재 프로젝트의 로그인 provider를 모두 적는다.
- 각 provider 옆에
issuer,client id,scopes,email 필수 여부,계정 연결 방식을 적는다. - Supabase에서 실제로 RLS가 보는 값이 무엇인지 확인한다. 대부분은
auth.uid()와 JWT claims다. - 아래 질문에 답한다.
custom:company-idp로 로그인한 사용자가
1) 어떤 Supabase user id가 되는가?
2) 어떤 organization에 들어가는가?
3) 어떤 RLS policy를 통과해야 하는가?
4) 계정이 기존 GitHub 로그인과 같은 email이면 병합되는가?
5) email이 없으면 우리 제품은 무엇으로 사용자를 식별하는가?
이 다섯 질문에 답하지 못하면 아직 Custom OIDC를 붙일 준비가 끝난 것이 아니다. 로그인 버튼은 빨리 만들 수 있지만, 신원 경계가 흐리면 나중에 권한 버그와 고객지원 비용으로 돌아온다.
7. 더 볼 자료
- Supabase Custom OAuth/OIDC Providers 문서: provider type,
custom:identifier, Dashboard/Admin API 설정을 확인할 때 본다. - Supabase Identity Linking 문서: 자동 linking, 수동 linking, unverified email, SAML SSO 제한을 확인할 때 본다.
- OpenID Connect Discovery 1.0: issuer와 discovery document가 왜 신뢰 루트인지 이해할 때 본다.
- RFC 7636 PKCE: authorization code flow를 모바일/브라우저 환경에서 안전하게 쓰는 이유를 확인할 때 본다.
중복 회피 메모
로컬 content/generated와 Supabase 최근 web_app_dev 글을 확인했다. 최근 글은 Supabase RLS 테스트, Realtime binary payload, TanStack DB 클라이언트 데이터 그래프, Pipelines/CDC, Auth URL, PostgREST JWT kid, 패스키 RP ID, Next.js 보안 릴리스와 캐시/프리패칭, Hono 입력 객체 경계 등을 다뤘다. 이번 글은 Supabase Auth를 다시 다루지만 패스키나 self-hosted URL, JWT key 검증, RLS 테스트 자체가 아니라 Custom OAuth/OIDC Provider를 외부 신원에서 Supabase user/RLS/제품 권한으로 번역하는 신원 경계 계약으로 해석한다.
핵심 출처
로그인하면 이 글을 북마크하고, 나만 보는 한 줄 메모를 남길 수 있어요.
댓글 0
최신순 ▾혹시 이 글을 읽는 동료 개발자가 있다면, GitHub으로 로그인하고 한 줄 흔적을 남겨줘요. (스팸 방지용 로그인이에요)