계정 통합 (Account Merge)
Source:
guide/account-merge.md· Live: https://docs.1pass.dev/guide/account-merge LLM-sanitized: internal links absolutized, VitePress containers → admonitions, line numbers in the Jump-to Index reference this rendered file (1-indexed).
📍 Jump-to Index
- L27-L46: ## 통합이 끝나면 어떻게 되나
- L47-L101: ## 세 가지 트리거
- L49-L56: ### T1 — 같은 기기에서 자동 통합 (device-link)
- L57-L67: ### T2 — 같은 이메일을 쓰는 다른 provider 로 로그인
- L68-L88: ### T3 — 본인이 직접 두 계정을 합치기
- L89-L101: ### 익명 계정 스왑 (anonymous swap) — 네이티브 전용
- L102-L117: ## 무엇이 끊어지나
- L118-L126: ## RP(서비스) 쪽에서는 어떻게 보이나
- L127-L130: ## 되돌릴 수 있나요
- L131-L138: ## 다음 단계
계정 통합 (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 를 사용할 수 있습니다.
💡 Tip: T3 완료는 웹 전용 (web-only) T3 의 OTP + 동의(consent) 완료 단계는 logi 웹의
/me/merge컨센트 화면에서 진행합니다 — 현재 웹 전용 이며 네이티브(iOS/Android) 앱에는 별도 T3 진입점이 없습니다. 이는 설계상의 결정입니다: T3 는 이미 존재하는 두 실계정을 OTP proof-of-possession + 명시 동의로 합치는 보안 민감 작업이고, 앱 내 계정 충돌은 이미 네이티브 동의 화면을 가진 익명 스왑(아래 참고)으로 라우팅되므로, 모바일에 OTP+동의 보안 흐름을 중복 구현하지 않습니다. (웹 동의 화면으로 보내는 네이티브 딥링크는 향후 단계로 추적 중입니다.)
보안 세부사항은 Merge Idempotency 와 Threat Model 참고.
익명 계정 스왑 (anonymous swap) — 네이티브 전용
익명으로 둘러보던 사용자가 SSO 로 로그인하면서 기존 익명 계정과 충돌이 생기는 경우, 네이티브 (iOS/Android) 앱은 동의 화면을 통해 익명 계정을 SSO 계정으로 스왑합니다 (iOS AccountSwitchSheet, Android 대응 화면).
💡 Tip: 익명 스왑은 웹이 아닌 네이티브에서만 익명 계정 스왑은 네이티브 전용 이며, 웹에는 동등한 UI 를 제공하지 않습니다. 이는 단순히 "웹이 토큰을 못 만든다" 가 아니라 의도된 제품·보안 경계 입니다: 익명 스왑은 Apple/Google 의
identity_token을 소비하는데, 웹 버전을 만들면 웹 SSO 콜백을 스왑 로직으로 끌어들여 웹 콜백 / 계정 연결(account-linking) 공격면을 드물게 발생하는 "웹-익명 충돌" 한 케이스를 위해 확장하게 됩니다. 따라서 익명 스왑은 네이티브 경계로 유지합니다.
무엇이 끊어지나
보안을 위해, 흡수된 계정이 갖고 있던 모든 자격증명 은 통합 transaction 안에서 즉시 무효화됩니다 (9가지 항목):
- Access Token / Refresh Token / Authorization Code
- Personal API Key (PAK)
- OAuth Grant (RP 별)
- 활성 세션 (브라우저 쿠키)
- 디바이스 자격증명 (앱 keychain)
- WebAuthn 자격증명 (passkey)
- TOTP / 백업 코드
- 진행 중인 비밀번호 재설정 / 이메일 변경 토큰
- 진행 중인 통합 OTP 토큰
위 무효화는 흡수된 쪽에만 적용됩니다. 기본 계정의 자격증명은 그대로 유지되므로, 통합 직후에도 로그아웃되거나 다시 로그인할 필요는 없습니다.
RP(서비스) 쪽에서는 어떻게 보이나
💡 Tip: 📋 프로덕션 정규화 정책 프로덕션 환경 (예: 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 에서 다룹니다.