WebView에서 인증 팝업이 조용히 차단되는 이유 (1): window.open() 차단 메커니즘
WebView의 window.open() silent blocking
난이도: ★★★☆☆
연관 노트: WebView에서 서드파티 인증이 무반응일 때 (2)
배경
서드파티 인증 SDK를 연동한 서비스에서, 특정 앱의 인앱 브라우저로 접근한 사용자들이 “인증 버튼을 눌러도 아무 반응이 없다”는 신고를 여럿 받았다. 일반 브라우저에서는 정상 동작했다.
인앱 브라우저라 디버깅 자체가 어려웠다. DevTools를 열 수 없고, console.log를 찍어도 볼 방법이 없다. 사용자 기기에서 직접 재현하며 브레이크포인트를 걸 수도 없다. 결국 서버 로그와 SDK 벤더사(토스) 담당자 응답에 의존해서 구간을 좁혔다.
핵심 요약
Toss 인증 SDK의 preparePopup()은 내부적으로 현재 브라우저 위에 새로운 팝업을 띄운다. Safari처럼 비동기 호출 이후 window.open()을 막는 환경에서 미리 팝업을 열어두기 위한 우회 방법이다.
인앱 브라우저(WebView)에서는 이 팝업 생성 자체를 막는다. 에러를 던지지 않고 조용히 차단하기 때문에, 이후 코드가 계속 실행되다가 팝업에 authUrl을 로드하려는 순간 아무것도 없어 무반응으로 보인다.
왜 헷갈렸나
서버 로그에서 인증 API 200 OK를 확인했다. 즉 요청 자체는 성공했다. 그런데도 화면이 무반응이었다.
- “API가 실패한 건가?” → 아니다, 서버는 정상 응답
- “SDK 로드가 안 된 건가?” → 아니다, 일반 브라우저에선 된다
- “어디서 막힌 건가?” → 에러가 없으니 알 수 없다
이게 silent blocking의 함정이다. 예외가 없으니 스택 트레이스도 없고, 콘솔도 조용하다. 실패 지점이 코드 상으로 추적되지 않는다.
메커니즘
preparePopup()이 필요한 이유
브라우저는 window.open()을 사용자 제스처(클릭) 시점에만 허용한다. 비동기 API 호출 이후에 window.open()을 호출하면 “사용자 제스처 없음”으로 판단해 차단한다.
Toss 인증 SDK 문서에도 이 점을 명시하고 있다:
⚠️ 비동기 호출이 preparePopup보다 먼저 발생하면 safari 브라우저에서 정상동작 하지 않을 수 있습니다.
그래서 클릭 시점(동기)에 미리 빈 팝업을 열어두고, API 응답 후 그 팝업에 authUrl을 로드한다.
var tossCert = TossCert();
tossCert.preparePopup(); // ← 클릭 시점에 동기적으로 window.open("") 호출
getTxId().then(function (response) { // ← 비동기 API
tossCert.start({
authUrl: response.authUrl,
txId: response.txId,
onSuccess: function () { ... },
onFail: function (error) { ... },
});
});
WebView에서의 흐름
[팝업 방식 정상 흐름 — 일반 브라우저]
사용자 클릭 (user gesture)
│
▼
preparePopup()
└─ window.open("") → 빈 팝업 열림 ✓ ← 동기, 즉시 실행
API 호출 (비동기)
│
▼
tossCert.start({ authUrl, txId, onSuccess, onFail })
└─ 열려있는 팝업에 authUrl 로드 ✓
[WebView에서의 흐름]
사용자 클릭
│
▼
preparePopup()
└─ window.open("") → WebView가 조용히 차단 ✗ ← 에러 없음
API 호출 (비동기)
│
▼
tossCert.start({ authUrl, txId, onSuccess, onFail })
└─ 팝업이 없으니 로드할 곳 없음 → 무반응
WebView가 차단하는 이유
인앱 브라우저는 앱 자체가 WebView를 감싸는 구조다. 앱이 허용하지 않으면 내부 WebView에서 새 창을 열 수 없다. 이 제한이 window.open()에도 적용된다.
차단 방식이 예외 throw가 아닌 null 반환 또는 no-op이기 때문에, 호출한 쪽에서 성공으로 착각한다.
다음에 이 상황을 만나면
트리거 조건: 특정 인앱 브라우저에서만 팝업이 안 뜨고, 에러 없이 무반응
- 서버 로그로 API 호출 성공 여부 먼저 확인 (DevTools가 안 보이는 경우 대비)
- 의심 환경의 UA 확인 (
navigator.userAgent에서 앱 식별자 탐색) - SDK가 팝업 외에 리다이렉트 방식을 지원하는지 확인 → (2) 편 참고
- 네이티브 앱이라면 WebView 설정에서 팝업 허용 가능 여부 확인
커넥팅 닷
← 선행 개념 (이걸 알아야 이해된다)
- 브라우저 팝업 차단 정책 — 사용자 제스처 없는
window.open()은 차단된다.preparePopup()이 존재하는 이유의 뿌리 - WebView 격리 모델 — 네이티브 앱이 감싼 WebView는 앱이 허용한 것만 실행 가능. 일반 브라우저와 다른 보안 경계
→ 확장 개념 (여기서 더 나아가면)
- WebView에서 서드파티 인증이 무반응일 때 (2) — 팝업이 막혔을 때 리다이렉트 방식으로 우회하는 방법
- Feature Detection vs UA Detection — 기능을 직접 탐지하는 방식(
window.open()결과 체크)과 UA로 환경을 판별하는 방식의 트레이드오프
↔ 같은 원리가 적용되는 곳
- 광고 픽셀은 UTM이 사라져도 어떻게 전환을 잡는가 — 브라우저/앱 정책으로 3rd party 동작이 제한되는 같은 맥락
- Silent failure 패턴 — 에러를 던지지 않고 조용히 실패하는 API (null 반환, no-op). 디버깅이 어려운 이유가 여기에 있음
참고
- Toss 인증 공식 문서 — Case 1. 브라우저에 표준창 띄우기 — preparePopup() 주의사항 명시
- MDN: window.open() — 팝업 차단 조건과 user gesture requirement
- HTML 스펙: Transient activation — 사용자 제스처가 유효한 시간 윈도우 정의