테마
계정 통합 (Account Merge)
한 사람이 시간이 흐르면서 같은 logi 계정을 여러 개 갖게 되는 일은 흔합니다. 한 기기에서는 Apple 로, 다른 기기에서는 Google 로 처음 로그인했을 수도 있고, 익명으로 먼저 둘러보다가 나중에 SSO 를 연결하기 전에 다른 디바이스에서 또 익명 가입을 했을 수도 있습니다. 단순히 사용하는 이메일 주소가 두 개라서일 수도 있고요.
logi 는 이렇게 갈라진 계정을 하나로 합치는 세 가지 경로를 제공합니다. 여러분이 RP(서비스) 에 쌓아둔 도메인 데이터 — 기록, 노트, 결제 이력 등 — 는 그대로 보존됩니다. 통합은 언제 일어나든 audit log 와 identity_links 기록에 남아 있어, 본인 또는 운영자가 나중에라도 어떤 계정이 합쳐졌는지 확인할 수 있습니다.
통합이 끝나면 어떻게 되나
통합이 성공하면 두 계정은 기본 계정(survivor) 하나로 정리되고, 흡수된 쪽(linked_user_id) 은 더 이상 새 토큰을 발급받지 않습니다. 모든 RP 는 이후 기본 계정의 canonical 식별자(survivor_canonical_sub) 만 보게 됩니다.
내부적으로는 identity_links 테이블에 한 줄이 추가됩니다:
primary_user_id (= 기본 계정, survivor)
linked_user_id (= 흡수된 계정)
merged_via ("t1_device_link" | "t2_email_match" | "t3_otp")
idempotency_key (재시도 안전)흡수된 계정의 row 자체는 삭제되지 않습니다 — 통합 흔적은 그대로 남고, RP 가 받는 user.merged 웹훅을 통해 RP 쪽 외래키(logi_identity_links) 가 갱신됩니다.
설계상의 약속:
- 도메인 데이터는 보존 — RP 가 갖고 있는 토너먼트 기록, 노트, 결제 이력 등은 canonical 해석을 통해 그대로 살아남습니다. (다만 흡수된 계정의 세션 토큰·앱 keychain·브라우저 쿠키 는 보안상 즉시 무효화됩니다. 자세한 범위는 무엇이 끊어지나 참고.)
- 재시도해도 안전 — 같은
idempotency_key로 두 번 호출해도 결과는 동일하며, 동시성 충돌 시:already_processed가 반환됩니다. - 체인/사이클 금지 —
A→B와B→C같은 연쇄 통합은 DB 레벨 트리거로 차단됩니다. 기본 계정은 항상 한 번의 hop 안에 결정됩니다.
세 가지 트리거
T1 — 같은 기기에서 자동 통합 (device-link)
같은 디바이스에서 Apple SSO 와 Google SSO 를 모두 성공적으로 거치면, logi 는 두 계정이 같은 사람이라고 판단할 수 있는 충분한 근거를 갖게 됩니다 (디바이스 소유가 곧 본인 확인 역할). 이 경우 추가 확인 없이 merged_via=t1_device_link 로 합쳐집니다.
사용자 입장에서는 두 번째 SSO 직후 "두 계정이 합쳐졌습니다" 안내를 한 번 보는 것으로 마무리됩니다.
자세한 흐름은 T1 / T2 / T3 트리거 시퀀스 참고.
T2 — 같은 이메일을 쓰는 다른 provider 로 로그인
Apple 로 먼저 가입한 분이 나중에 Google SSO 로 처음 로그인할 때 (혹은 그 반대), 두 provider 가 반환한 이메일이 양쪽 모두 verified 상태로 일치 하면 logi 는 이를 같은 사람의 두 번째 SSO 로 해석합니다.
내부적으로는:
- 새 SSO 콜백에서
Auth::ProviderMatcher가email_address로 기존 계정을 찾습니다. - 기존 계정이 다른 provider 의
*_sub를 이미 갖고 있으면, 임시 계정(새 provider sub 만 가진 ghost row) 을 만든 뒤 곧바로MergeService로 기본 계정에 흡수시킵니다. - 임시 계정이 갖고 있던 sub/email 은 같은 transaction 안에서 기본 계정으로 옮겨집니다.
사용자 입장에서는 평범한 "로그인 성공" 으로 보입니다. 한쪽 이메일이 verified 가 아니거나 두 이메일이 다르면 자동 통합이 일어나지 않습니다.
T3 — 본인이 직접 두 계정을 합치기
다른 기기에서 서로 다른 SSO 로 시작했던 두 계정을 본인이 명시적으로 합치고 싶을 때 사용하는 경로입니다. logi 의 /me/merge 컨센트 화면에서:
- 단계 A — 합치려는 상대 계정의 이메일을 입력. logi 가 해당 이메일(verified contact_email) 로 6자리 OTP 를 발송합니다.
- 단계 B — 받은 OTP 를 입력. 검증되면 즉시 통합이 실행되고, 현재 로그인된 계정이 기본 계정이 됩니다.
- 단계 C — 통합 완료 안내.
T3 는 양쪽 모두에서 본인 확인을 받습니다 (dual proof-of-possession). 현재 세션은 쿠키 또는 PAK 로, 흡수되는 쪽은 verified 이메일로 도착한 OTP 로 각각 확인합니다. 둘 중 하나라도 빠지면 다음 단계로 넘어가지 않습니다.
같은 디바이스에서 두 세션이 모두 활성인 경우 OTP 대신 session_token PoP 를 사용할 수 있습니다.
보안 세부사항은 Merge Idempotency 와 Threat Model 참고.
무엇이 끊어지나
보안을 위해, 흡수된 계정이 갖고 있던 모든 자격증명 은 통합 transaction 안에서 즉시 무효화됩니다 (9가지 항목):
- Access Token / Refresh Token / Authorization Code
- Personal API Key (PAK)
- OAuth Grant (RP 별)
- 활성 세션 (브라우저 쿠키)
- 디바이스 자격증명 (앱 keychain)
- WebAuthn 자격증명 (passkey)
- TOTP / 백업 코드
- 진행 중인 비밀번호 재설정 / 이메일 변경 토큰
- 진행 중인 통합 OTP 토큰
위 무효화는 흡수된 쪽에만 적용됩니다. 기본 계정의 자격증명은 그대로 유지되므로, 통합 직후에도 로그아웃되거나 다시 로그인할 필요는 없습니다.
RP(서비스) 쪽에서는 어떻게 보이나
📋 프로덕션 정규화 정책
프로덕션 환경 (예: Easy Bracket 프로덕션 RP) 은 2026-05-11 부터 ENFORCE_CANONICAL_RESOLUTION=true 로 운영됩니다. RP 는 머지된 사용자에 대해 항상 canonical_sub 으로 정규화된 sub claim 을 받으며, stale 한 merged_sub 으로 발급된 토큰을 검증하면 즉시 거부됩니다. RP 측 로직은 반드시 canonical resolver 를 통과시키세요.
RP 는 user.merged 웹훅을 받습니다. 페이로드 구조와 idempotency 계약은 Event Delivery 참고. 웹훅을 받은 RP 는 자기 DB 에 logi_identity_links(primary_user_id, linked_user_id) 한 줄을 INSERT 하고, 이후의 모든 user 조회는 canonical resolver 를 통과시킵니다.
logi 가 발급한 access token 은 자동으로 폐기됩니다. 다만 RP 가 자체 발급한 세션 토큰은 다음 token 회전 또는 polling 사이클이 돌아오기 전까지 잠깐 유효한 상태로 남을 수 있습니다 — 이 짧은 갭은 명시적으로 허용된 동작이며, 처리 방법은 RP Migration Guide 에 정리되어 있습니다.
되돌릴 수 있나요
현재 v3 에서는 통합 해제(unmerge) 기능을 제공하지 않습니다. 통합 흔적은 audit log 와 identity_links 에 그대로 남아 추적은 가능하지만, 사용자나 운영자가 "분리" 를 실행할 수 있는 API 는 별도로 두지 않았습니다. 이는 데이터 일관성과 보안 측면에서 의도된 설계입니다 — 자세한 근거와 forensic 복구가 필요한 경우의 운영 절차는 Rollback Policy 에서 다룹니다.