소셜 로그인 3종 완주 — Google·Apple 출시 (2026-08-21)
- 기간: 2026-08-21 ~ 08-22 (세션 2개 — Phase 0~2 / Phase 3~5 승계)
- 랜딩: PR #507(Phase 0 법률 v3·런북·도구) · #533(Apple secret rotation 기입) · #535(Phase 2 동의 전환, 마이그레이션
20260821120000) · #547(Phase 4 gate 3종 + 스모크 편입) · #574(Phase 5 발견 — iOS 연결 후 리셋 제거 + 기록). 마이그레이션 1건 Production 적용 - 런북:
docs/platform/social-login-rollout-runbook.md(정책·코드 정본은social-auth.md) - 도구:
scripts/generate-apple-client-secret.mjs(무의존 ES256 JWT) ·scripts/check-social-provider-config.mjs(authorize 프로브,--gated)
1. 배경
Kakao·Google·Apple 로그인 코드는 #345(provider 독립 구조 + 계정 연결 + iOS 콜백)에서 3종 모두 구현돼 있었지만, production은 Kakao만 열려 있었다. app-config.js의 socialAuthProviders: ["kakao"] gate, Supabase의 Google/Apple 미활성, 그리고 게시된 개인정보처리방침 v1·v2가 불변 문서로 Kakao만 명시한다는 사실 — 이 셋이 "v3 게시·동의 전환 전에는 Google/Apple을 production에서 활성화하지 않는다"는 문서화된 정책으로 묶여 있었다.
오너가 2026-08-21 이 일을 정식 트랙으로 승격했다. 완주 조건은 하나: Apple·Google· Kakao 3종 로그인이 production에서 정상 작동한다. 진행 절차는 HQ 보고 → 셋업 티칭 수신 → 완주까지 자율 진행.
2. 문제 제기
남은 일은 전부 코드 밖에 있었다
잔여 코드 작업은 gate 전환과 그 계약 테스트뿐이었고, 차단 요소 넷은 외부 콘솔과 문서였다: ① Google Cloud OAuth client ② Apple Developer의 Services ID·Sign in with Apple 키·6개월 만료 client secret ③ Supabase provider 활성화 + Allow manual linking ④ 개인정보처리방침 v3 게시와 동의 버전 전환.
순서를 뒤바꾸면 사고가 난다
v3 게시 전에 Supabase에서 provider를 켜면 UI 버튼이 없어도 authorize 엔드포인트가 공개적으로 동작해 방침이 커버하지 않는 수집 경로가 열린다. 반대로 gate를 provider 설정 전에 열면 미구성 provider 버튼이 사용자에게 노출된다. 동의 전환은 registry (CURRENT_LEGAL_DOCUMENT_VERSIONS)만이 아니라 DB(onboarding_consent_requirements())도 같이 바꿔야 하는 마이그레이션이다.
검증은 이원이다
provider가 켜졌는지는 자격증명 없이 기계로 잴 수 있지만, 실계정 OAuth 왕복과 계정 연결은 자동화로 대체되지 않는다. 그리고 Apple secret은 만료가 있어 일회성 출시가 아니라 반복 운영 절차가 필요하다.
3. 해결 방안
원칙
- 순서 게이트 고정: Phase 0 v3 게시 → 1 콘솔 준비 → 2 동의 전환 → 3 Supabase 활성화 → 4 기계 검증 + gate 전환 → 5 오너 실계정. 뒤바꾸지 않는다.
- 역할 분리: 콘솔 계정 권한이 필요한 단계는 [오너], PR로 실행·검증하는 단계는 [세션]. 자격증명(Google secret·Apple JWT·
.p8)은 오너가 보관하고 세션은 만지지 않는다 — JWT 생성 스크립트도 오너 터미널에서 돌린다. - 기계 검증은 자격증명 없이: Supabase authorize 엔드포인트가 활성 provider는 외부 인증 화면으로 302, 비활성은 400을 돌려준다는 사실로 프로브를 만들고, gate가 노출하는 provider는 서버에서도 활성화돼 있어야 한다는 불변식을 스모크에 편입한다.
- 완주 판정 = 기계 검증 전부 그린 + 오너 실계정 확인.
접근
런북 한 장에 콘솔별 절차·공통 값(Supabase callback 하나)·검증 명령·rotation 절차를 담고, 도구 2종으로 사람이 실수할 자리를 줄였다. PR은 순서 게이트 앞 단계에서 미리 준비하되 머지는 게이트 조건이 실측으로 충족된 뒤에만 한다.
4. 적용한 내용
Phase 0 — v3 문서·런북·도구 (#507)
terms-v3.html·privacy-v3.html 초안(시행일 08-28 초안, 외부 법적 검토 권장 플래그), registry v3 등재(CURRENT는 v2 유지 = 무동작), 런북, 도구 2종. 실측으로 확정한 전제: requires_onboarding은 동의 버전과 무관(기존 유저를 잠그지 않고 재동의 UI도 없음 — "신규 수집은 해당 로그인을 쓰는 이용자에게만 발생"하므로 고지로 충분하다는 전제), check:legal-documents가 terms/privacy 버전 쌍을 강제(그래서 terms-v3도 함께). 게시 확인: www.barbelic.com/legal/{terms,privacy}-v3 200·Google/Apple 명시.
Phase 1 — 외부 콘솔 준비 (오너, #533)
Google: Web application OAuth client, consent screen In production(Testing이면 등록 계정만 로그인되는 함정), redirect URI는 Supabase callback 하나. Apple: App ID com.dekerd.barbelic + Services ID com.dekerd.barbelic.web(Configure — Supabase 도메인·Return URL) + Sign in with Apple 키 .p8 + client secret JWT(만료 2027-02-17, 2주 전 알림 2027-02-03) → 런북 rotation 표 기입. JWT는 .p8을 올리는 게 아니라 스크립트 출력 텍스트를 Supabase Secret Key 칸에 넣는 값이다.
Phase 2 — 동의 버전 전환 (#535, 20260821120000)
원칙은 시행일(08-28) 이후였으나 오너 결정으로 게시 직후 전환: 신규 가입자는 게시된 v3에 직접 동의하고 들어오므로 동의 공백이 없고, 시행일은 법정 기한이 아니라 약관 제3조의 자체 7일 예고 관례였다(형식상 흠결은 오너 재량으로 수용, 런북에 기록). CURRENT v3 flip + onboarding_consent_requirements() v3 마이그레이션 + pgTAP barbelic_legal_v2→v3 개정 + 계약 3파일. Production 적용 + check:remote-schema EXIT=0.
Phase 3 — Supabase provider 활성화 (오너, 08-22)
Google Client ID/secret, Apple Client IDs com.dekerd.barbelic.web + JWT 입력·Enable. Allow manual linking·Site URL·redirect allowlist는 Management API 읽기(부울만 출력)로 이미 정상임을 먼저 확인해 오너 잔여를 두 칸으로 좁혔다. 프로브 3종 PASS.
Phase 4 — gate 3종 전환 + 스모크 편입 (#547)
socialAuthProviders: ["kakao", "google", "apple"], 프로브 --gated 모드(gate가 노출하는 provider만 검사 — UI 노출 ⊆ 서버 활성 불변식), external-auth-smoke.yml에 CASE-003 앞 gated 프로브 스텝, 계약 테스트 갱신(socialOAuthProviders·appContainer· externalAuthE2eConfig + #546이 들여온 appConfigEnv), social-auth.md·런북 기록. 드래프트로 먼저 준비 → 프로브 3종 PASS 실측 뒤 머지(main ee81aa4c) → 라이브 번들 socialAuthProviders:[kakao,google,apple] + production 로그인 화면 버튼 3종 DOM 실측 → external-auth-smoke 수동 발사 success(gated 스텝 + CASE-003).
Phase 5 — 오너 실계정 확인 + 발견 사항 (#574)
오너가 Google 로그인 → 같은 계정 진입, 카카오 재연결, Apple 로그인 화면까지 확인하며 세 가지를 발견했다(런북 "Phase 5 발견 사항"에 상세 기록).
- 미확인 계정의 Kakao identity가 Google 로그인 때 제거됨. 오너 계정은 2026-05 생성 당시 Kakao가 검증 이메일을 주지 않아 Supabase 기준 "이메일 미확인"이었고 (
auth.users.emailNULL), 같은 이메일의 Google(검증됨)이 자동 연결되자 Supabase Auth의 계정 선점 방어(RemoveUnconfirmedIdentities)가 미확인 identity(Kakao)를 삭제했다. 같은 계정·데이터 무손실. 복구 = 로그인 연결 > 카카오(manual linking) — 재연결된 identity는 검증 이메일을 들고 왔고 계정이 확인됨이 되어 재발하지 않는다. 검증 이메일로 가입한 다른 Kakao 계정에는 해당 없음. - iOS 계정 연결 완료 후 WebView 리셋(재부팅처럼 보임). 네이티브
nativeAuthComplete처리가 sign-in/link를 구분하지 않고 앱 진입으로 리셋했다. 수정(#574): 주입 스크립트가action을 함께 보내고 link면 리셋을 건너뛴다. 웹 무변경, iOS 빌드·배포가 있어야 기기 반영. - Apple "이메일 가리기"는 끌 수 없다(플랫폼 보장·심사 지침 4.8). 릴레이 주소는 검증된 고유 이메일이고 계정 식별은 Apple
sub라 재로그인은 같은 계정. Supabase 자동 연결 규칙: 새 provider의 검증된 이메일이 기존 어느 identity의 이메일이든(또는users.email) 같으면 그 계정에 붙는다 — Kakao a + 연결된 Google b 계정에 Apple이 a나 b를 공유하면 같은 계정, 릴레이/제3 이메일이면 새 계정(→ 로그인 연결로 합친다).
주요 결정과 그 근거
- 시행일 전 조기 전환(오너) — 위 Phase 2 근거. 시행일 관례는 예고이지 법정 기한이 아니다.
- Apple은 웹 OAuth(PKCE 브라우저 세션) — OS 무관, Windows 사용자도 Apple ID + SMS 2FA로 로그인. iOS 앱의 Apple 로그인만 같은 날 네이티브 시트로 전환(§7).
- 자격증명은 세션이 만지지 않는다 — JWT가 채팅에 붙으면 트랜스크립트에 잔존한다 (무력화는 키 revoke·재발급뿐).
- gate PR은 미리, 머지는 프로브 뒤 — 순서 게이트를 사람이 아니라 실측 조건으로 지킨다.
- 롤백은 gate에서 해당 provider만 제거 — Supabase 비활성화보다 빠르고 안전하다.
- Kakao 이메일 필수 동의·Apple 공유 강제는 앱 코드 밖이다 — 전자는 Kakao 콘솔 (비즈 앱 조건 추정, 미실행), 후자는 불가. 둘 다 "로그인 연결" UI가 해답.
작업 중 드러난 것
- 마이그레이션 번호 선점 2회(080000→110000→120000): HQ 승인 뒤에도 CI 풀 레인 ~7분의 클레임-랜딩 창마다 타 트랙이 선점했다. HQ 단기 동결(15분)로 해소한 선례. 재번호는 파일명·
schema.sqlappend 위치·EXPECTED_LATEST_MIGRATION·pending-changes 문자열 4곳. - 크로스트랙 계약 테스트: #546(환경 분리)이
app-config.js를 손대며appConfigEnv.test.mjs에socialAuthProviders=["kakao"]하드코딩을 들여왔고, 리베이스 뒤 CI verify가 잡았다. 같은 파일을 건드린 PR이 머지되면 리베이스 + 그 PR이 추가한 테스트까지 grep. - sourceSliceAnchors 게이트가 새 계약 테스트의
indexOf("- name: Run CASE-003")앵커를 잡았다 —[\s\S]*regex 순서 단언으로 재작성. - minify는 배열 리터럴을 백틱으로 인용한다 — 라이브 번들에서
"kakao","google","apple"큰따옴표 grep 폴링은 영원히 0. 배포 확인은socialAuthProviders:[…]패턴으로. - Production 읽기 실측 경로: Supabase Management API
config/auth(부울 키만 출력, secret 키 미출력)와database/query(마스킹 SELECT)로 provider 활성·identity 상태를 확인했다.auth.audit_log_entries는 0행, analyticsauth_logs는 'Backend error'라 로그 경로는 쓸 수 없었고 SQL 스냅샷(identities·sessions·메타 키)으로 판정했다. - PS 5.1
Set-Content -Encoding UTF8은 BOM을 심는다 / PR 생성 직후 CI 체크 미발화 → rebase synchronize로 재트리거 / 오너가 Downloads에서 상대경로로 스크립트를 실행해 실패 → 절대경로 명령으로 안내. - 세션 승계: 구 세션이 계정 한도로 종료돼 새 세션이 트랜스크립트(정본)·메모리로 인계받았고 HQ에 "received HQ" 정본 보고 1통으로 편입했다.
5. 적용 결과
- Production: Supabase Google·Apple enabled(Kakao 유지), authorize 프로브 3종 PASS,
www.barbelic.com로그인 화면 버튼 3종, 법률 v3 게시·CURRENT v3, 마이그레이션20260821120000적용·check:remote-schemaEXIT=0, external-auth-smoke(gated 프로브 + CASE-003) success. - 검증: PR별 CI scope·verify·migration-smoke 그린(#535·#547·#574), 로컬 verify 레인 게이트 전부 통과(
check1,633/1,633 시점), 계약 테스트socialOAuthProviders·appContainer·externalAuthE2eConfig·appConfigEnv·iosNativeAuthContract. - 오너 실계정: Google 로그인 → 기존 계정 진입, 카카오 재연결(identity 2, 검증 이메일), Apple 로그인 화면 확인 → 오너 완주 판정(08-22).
- DB 접촉은 마이그레이션 1건뿐(Phase 2). Phase 4·5는 설정·클라이언트·iOS·문서만.
6. 이번 개선으로 향상된 것
로그인 방식이 셋이다
카카오 없이도 Google·Apple ID로 가입·로그인할 수 있고, 한 계정에 여러 방식을 붙일 수 있다(내 정보 > 로그인 연결).
출시 상태가 기계로 잠겼다
gate가 노출하는 provider는 서버에서도 활성화돼 있어야 한다는 불변식을 스모크가 매 실행 검사한다. gate를 먼저 열거나 provider를 끄면 레드다.
운영 절차가 문서다
콘솔 절차·공통 값·검증 명령·Apple secret rotation(6개월)·Phase 5 발견 사항이 런북 한 장에 있다. 다음 rotation(2027-02-17 전)은 런북 절차 5분짜리다.
계정 동일성의 규칙을 알게 됐다
"같은 사람"은 auth.users.id 하나이고, 자동 연결은 검증 이메일 일치, 나머지는 로그인 연결 — 그리고 미확인 계정의 엣지까지 실측으로 확인해 기록했다.
7. 후속 — iOS 네이티브 Sign in with Apple (2026-08-22)
오너 결정으로 "남은 것"에 있던 네이티브 SIWA를 같은 날 바로 이어 구현했다. 범위는 iOS 앱의 Apple 로그인만 — Apple 계정 연결과 Kakao·Google은 종전 웹 OAuth (ASWebAuthenticationSession) 그대로다. 웹 쪽은 이미 completeNativeSocialLogin이 detail.idToken 경로(signInWithIdToken + nonce)를 갖고 있어 무변경이고, iOS만 바뀌었다.
MainViewController.swift:startSocialLogin에서provider == .apple && action == .signIn이면ASAuthorizationController시스템 시트로 분기. 무작위 nonce(32바이트 hex) → SHA-256 hex를 요청 nonce에, 성공 시 identity token + 원문 nonce를barbelic:native-authdetail{ provider, idToken, nonce }로 전달(기존 콜백 dispatcher를 detail 객체를 받도록 일반화). 취소(ASAuthorizationError.canceled)는 "로그인이 취소되었습니다.", 그 외는 "Apple 로그인을 완료하지 못했습니다." 토큰은 로그·커스텀 스킴에 싣지 않는다(계약 테스트가id_token|access_token|refresh_token리터럴·토큰 print를 금지).App/App.entitlements(com.apple.developer.applesignin) + pbxprojCODE_SIGN_ENTITLEMENTS두 구성.- 전제: Supabase Apple Client IDs =
com.dekerd.barbelic.web, com.dekerd.barbelic(identity token audience가 번들 ID) — 오너 콘솔 한 칸. 맥북 Xcode에서 capability 확인·빌드·실기기 확인·배포는 런북 "iOS 네이티브 Sign in with Apple" 절. - Windows에서는 Swift를 컴파일할 수 없어 계약 테스트(
iosNativeAuthContract)로 텍스트를 잠그고, 실제 동작 검증은 맥북 빌드 몫이다.
남은 것
- Apple client secret rotation: 만료 2027-02-17, 알림 2027-02-03 — 런북 절차.
- iOS 빌드·배포(맥북): #574(연결 후 리셋 제거) + 네이티브 SIWA를 기기에 반영하려면 앱 빌드가 필요하다. 그 전에 Supabase Apple Client IDs에 번들 ID 추가.
- Kakao 이메일 필수 동의: Kakao Developers 콘솔 설정(비즈 앱 조건 확인) — 앱 코드 무변경. 기존 계정엔 소급되지 않는다.
- 법적 검토: v3 초안은 법률 자문 없이 작성됐다. 기존 이용자 재동의 필요 여부는 법령 해석 사항(Phase 0 권장 플래그 유지).