Skip to Content
DecisionsADR-0004: native 간편인증을 PUSH 대신 App2App 직접 호출로 전환한다

ADR-0004: native 간편인증을 PUSH 대신 App2App 직접 호출로 전환한다

  • Status: Accepted (Amended by ADR-0005)
  • Date: 2026-08-31
  • 관련: HMH-8965, PR #5794 · 선행: HMH-8830 (#5711), HMH-8701
  • 배선: apps/native-app/src/app/native/assignment-simple-identity-verification.tsx

⚠️ 정정 (ADR-0005) — 아래 결정(UA로 App2App을 켠다)은 그대로 유효하지만, “검토한 대안” 표의 deviceBrowser 행 근거가 프로덕션에서 부정됐습니다. UA는 필요조건이지 충분조건이 아니었고, deviceBrowser도 함께 MWV로 보내야 App2App이 켜집니다(HMH-8978). 반증된 문장은 왜 그 판단이 그럴듯했는지 남기기 위해 고치지 않고 그대로 둡니다.

Context

native 간편인증(SIV)은 웹 SIV 페이지를 WebView로 내장하고, 그 안에서 eziok 표준창이 카카오·PASS 등 인증사업자 앱을 띄우는 구조입니다.

기존 방식은 표준창의 PUSH(WB) — 인증사업자 앱에 푸시를 보내 사용자가 알림에서 여는 방식이었습니다. 여기서 다음이 관측됐습니다.

  • 프로덕션 인증 결과 완료율 약 55% (eziok 서버 로그 기준). 나머지는 결과가 우리 쪽에 도달하지 않음
  • 원인은 Android의 프로세스 회수. 사용자가 앱을 떠나 있는 동안 OS가 우리 프로세스를 회수하면 복귀 시 결과를 받을 컨텍스트가 사라짐 (유실 비율 약 37:1)
  • HMH-8701의 복구 코드는 프로덕션에서 한 번도 실행되지 않았음 — 복구 표식을 남기는 분기 자체를 타지 못했기 때문. HMH-8830(#5711)에서 AppState 전환 기반으로 표식 시점을 옮겨 일부 복구

PUSH 방식은 앱을 떠나 있는 시간이 길수록 회수 확률이 올라갑니다. 사용자가 알림을 확인하고 앱을 여는 왕복 동안이 그 창입니다.

표준창 가이드(2.6 “인증사업자 인증 APP 직접호출”)는 대안으로 App2App — 표준창이 인증사업자 앱을 스킴으로 직접 호출하는 방식을 제공합니다. 왕복 시간이 짧아지고 사용자 조작 단계가 하나 줄어듭니다.

검토한 대안

대안채택 안 한 이유
PUSH 유지 + 복구 로직 강화이미 두 차례 시도(HMH-8701·HMH-8830). 복구는 유실을 줄일 뿐 유실 자체를 없애지 못한다. 근본 원인은 이탈 시간
인증사업자별로 App2App을 점진 적용불가능하다. App2App 스위치는 WebView의 userAgent인데, UA는 WebView 생성 시점에 고정된다. 사용자가 인증사업자를 고르는 건 그 WebView 안, 즉 그 이후다
비율(%) 기반 점진 롤아웃기술적으로는 가능하다 — WebView를 렌더하기 전에 플래그·버킷을 평가해 applicationNameForUserAgent에 넣을 값을 고르면 된다. 이번에는 채택하지 않았다: 플래그가 첫 렌더 전에 확정돼야 하고(늦으면 WebView가 잘못된 UA로 마운트된다), 판정 지표(hadEziokPrefix·결과 완료율)를 코호트별로 나눠 봐야 해 표본이 절반으로 준다. 표본 부족이 판정 지연으로 이어지는 쪽이 더 큰 위험이라고 봤다
deviceBrowser 파라미터를 MWV로 변경UA 없이는 표준창이 MWV를 받아도 WB(PUSH)로 강등한다. UA가 진짜 스위치이고 이건 효과가 없다. 웹 쪽 동작에 손대는 위험만 추가되므로 이번 범위에서 의도적으로 제외
이 판단은 틀렸다 → ADR-0005. 가이드의 자동승격 서술은 WB만 다루고 native가 실제로 보내던 MB는 대상이 아니었다. 둘 다 필요하다

Decision

SIV WebView의 userAgent에 MobileApp을 붙여 eziok App2App을 켠다.

  1. UA는 이 WebView에만 붙인다 — 전역 UA로 두지 않는다. App2App이 켜진 채 일반 브라우저 컨텍스트로 요청하면 앱 호출이 우선돼 “도메인 미등록 오류”가 날 수 있다 (가이드 경고)
  2. App2App이 켜지면 표준창은 인증사업자 앱 스킴 앞에 dream-eziok 접두어를 구분자 없이 붙여 navigation으로 흘려보낸다. onShouldStartLoadWithRequest에서 접두어를 떼고 남은 URL을 Linking.openURL로 OS에 위임한다. 접두어째 넘기면 스킴이 깨져 100% 실패한다
  3. 접두어가 붙었으면 남은 값이 http(s)여도 in-app 로드가 아니라 OS 위임 대상이다 (가이드의 앱 호출 우선 규칙)

검증 신호를 함께 심는다

이 결정은 효과가 났는지 원격으로 판정할 수 있어야 합니다. UA가 안 먹으면 조용히 예전 PUSH 동작으로 돌아가고 지표만 나빠지기 때문입니다.

  • siv_native_external_app_open 액션에 hadEziokPrefix 를 실어 보낸다. 이 값이 false로만 쌓이면 UA가 안 먹어 여전히 PUSH로 도는 것이다 — 이게 이 전환의 성패 판정자다
  • siv_native_backgrounded / siv_native_returned_from_external_app(elapsedMs)로 이탈 왕복 시간을 관측한다

중단 복구 표식은 이중으로 남긴다

딥링크 분기(동기)와 AppState background(비동기) 양쪽에서 남깁니다. 앱 전환이 급하면 JS 스레드가 AppState 이벤트를 받기 전에 프로세스가 회수될 수 있어, 동기 저장이 안전망입니다.

Linking.openURL 실패 시에는 표식을 지우되 포그라운드일 때만 지웁니다 — openURL의 reject는 플랫폼별 신뢰도가 낮아 인증앱이 실제로 떴는데도 reject되는 사례가 있고, 그때는 이미 백그라운드이므로 지우면 진짜 중단을 놓칩니다.

Consequences

좋아지는 것

  • 인증사업자 앱까지의 왕복이 짧아지고 사용자 조작이 한 단계 줄어, 프로세스 회수 창이 좁아진다
  • 결과 미도달의 근본 원인(이탈 시간)을 직접 줄인다 — 복구가 아니라 예방
  • hadEziokPrefix로 전환 성공 여부를 원격 판정할 수 있다

감수하는 것

  • 인증사업자별 게이팅은 불가능하고, 비율 롤아웃은 쓰지 않기로 했다. 결과적으로 이번 배포는 전량 전환이며, 문제가 생기면 되돌리는 수단은 롤백뿐이다. (비율 롤아웃 자체는 위 “검토한 대안” 참조 — 나중에 필요해지면 열려 있는 선택지다)
  • 접두어 파싱이 표준창 구현에 결합된다. eziok가 접두어 규칙을 바꾸면 조용히 깨진다 — hadEziokPrefix가 그 조기 경보다
  • UA 문자열(MobileApp)이 SIV WebView에만 붙는 국소 예외라, 이 화면의 WebView 설정을 일반 규칙으로 오해해 복사하면 다른 화면에서 도메인 미등록 오류를 만들 수 있다
  • 효과 검증에 시간이 걸린다. 결과 완료율은 사용자 행동 지표라 며칠치 표본이 쌓여야 판정 가능하다

이 결정을 다시 열어야 할 때

  • 배포 후 hadEziokPrefixfalse로만 쌓일 때 → UA가 안 먹는 것. 즉시 재검토
  • 결과 완료율이 55%에서 유의하게 오르지 않을 때 → 원인 가설(이탈 시간)이 틀렸다는 뜻
  • eziok가 표준창 App2App 규격(접두어·UA 조건)을 변경할 때
Last updated on