카카오톡, 에브리타임 등 인앱 브라우저에서 Google OAuth 로그인이 차단되는 문제를 분석하고, 앱별 URL 스킴을 활용한 외부 브라우저 리다이렉트 솔루션을 구현한 과정을 공유합니다.
📑 바로가기
🎯 문제 정의: 왜 카카오톡에서 구글 로그인이 안 될까?
대학 커뮤니티 에브리타임이나 카카오톡에서 공유된 링크를 클릭하면, 해당 앱 내부의 인앱 브라우저(In-App Browser)에서 웹페이지가 열립니다. 문제는 이 환경에서 Google OAuth 로그인이 완전히 차단된다는 것입니다.
사용자가 "Google로 계속하기" 버튼을 누르면, 다음과 같은 에러 메시지가 표시됩니다:
"403 disallowed_useragent"
이 브라우저 또는 앱이 안전하지 않을 수 있습니다.
강냉봇 서비스에서 이 문제는 심각했습니다. 대학생들은 주로 에브리타임이나 카카오톡을 통해 서비스 링크를 공유하는데, 해당 경로로 접근한 사용자들이 로그인 자체를 할 수 없었기 때문입니다.
🔍 원인 분석: Google의 보안 정책
2021년, Google의 결정
Google은 2021년 9월부터 임베디드 웹뷰(WebView)에서의 OAuth 로그인을 전면 차단했습니다. 이는 보안상의 이유 때문입니다.
🔒 WebView가 위험한 이유
- 키로깅 위험: 호스트 앱이 JavaScript를 주입하여 사용자 입력(비밀번호 등)을 가로챌 수 있음
- 중간자 공격: 앱이 Google과 사용자 사이의 통신을 변조할 수 있음
- 세션 탈취: 인증 후 발급된 토큰을 악의적인 앱이 가로챌 수 있음
- SSO 불가: 시스템 브라우저의 기존 로그인 세션을 활용할 수 없음
인앱 브라우저 감지 방법
각 앱의 인앱 브라우저는 고유한 User-Agent 문자열을 가지고 있습니다. 이를 분석하여 어떤 앱에서 접근했는지 식별할 수 있습니다.

// browserDetect.ts - User-Agent 기반 인앱 브라우저 감지
const inAppBrowserPatterns = [
{ pattern: /KAKAOTALK/i, name: 'KakaoTalk' },
{ pattern: /everytime/i, name: 'Everytime' },
{ pattern: /NAVER\(inapp|NAVER\//i, name: 'Naver' },
{ pattern: /Instagram/i, name: 'Instagram' },
{ pattern: /FBAN|FBAV|FB_IAB/i, name: 'Facebook' },
{ pattern: /Line\//i, name: 'LINE' },
{ pattern: /Twitter|X-Twitter/i, name: 'X (Twitter)' },
{ pattern: /; wv\)/i, name: 'Android WebView' },
];
export const detectInAppBrowser = () => {
const ua = navigator.userAgent;
for (const { pattern, name } of inAppBrowserPatterns) {
if (pattern.test(ua)) {
return { isInAppBrowser: true, browserName: name };
}
}
// iOS WebView: Safari 식별자가 없는 경우
if (/iPhone|iPad/.test(ua) && !/Safari\//.test(ua)) {
return { isInAppBrowser: true, browserName: 'iOS WebView' };
}
return { isInAppBrowser: false, browserName: null };
};
🔥 기술적 도전과제
1. 플랫폼별 외부 브라우저 열기 방법의 차이
가장 큰 난관은 Android와 iOS의 동작 방식이 완전히 다르다는 점이었습니다. 더 나아가, 같은 iOS라도 앱마다 지원하는 URL 스킴이 달랐습니다.
| 플랫폼 | 방법 | 문제점 |
|---|---|---|
| Android | Intent 스킴 | Chrome 미설치 시 실패 |
| iOS Safari | window.open() | 인앱 브라우저에서 무시됨 |
| iOS 카카오톡 | 전용 URL 스킴 | 문서화되지 않음 |
2. iOS의 근본적 제한
iOS에서는 JavaScript로 Safari를 강제로 실행하는 것이 불가능합니다. Android처럼 Intent 스킴으로 외부 앱을 호출할 수 있는 표준화된 방법이 없기 때문입니다.
window.open(url, '_blank')는 iOS 인앱 브라우저에서
같은 WebView 내에서 열리거나 완전히 무시됩니다.
3. 사용자 경험 고려
기술적으로 불가능한 경우에도 사용자가 당황하지 않도록, 명확한 안내와 대안(링크 복사, 게스트 모드 등)을 제공해야 했습니다.
💡 해결 과정
해결책 1: 앱별 URL 스킴 활용
조사 결과, 일부 앱들은 외부 브라우저로 열기 위한 자체 URL 스킴을 제공하고 있었습니다. 특히 카카오톡의 경우 공식 스킴이 존재했습니다.
🔗 발견한 앱별 URL 스킴
- 카카오톡:
kakaotalk://web/openExternal?url={encodedUrl} - 라인:
line://nv/article?url={encodedUrl} - 네이버:
naversearchapp://open?url={encodedUrl}

💻 플랫폼별 분기 처리 구현
// browserDetect.ts - 외부 브라우저 열기 로직
export const openInExternalBrowser = (
url?: string,
browserInfo?: InAppBrowserInfo
): boolean => {
const targetUrl = url || window.location.href;
const info = browserInfo || detectInAppBrowser();
// Android: Intent 스킴 사용 (시스템 기본 브라우저)
if (isAndroid()) {
const intentUrl = `intent://${targetUrl.replace(/^https?:\/\//, '')}` +
`#Intent;scheme=https;action=android.intent.action.VIEW;` +
`S.browser_fallback_url=${encodeURIComponent(targetUrl)};end`;
window.location.href = intentUrl;
return true;
}
// iOS: 앱별 URL 스킴 분기
if (isIOS()) {
const encodedUrl = encodeURIComponent(targetUrl);
switch (info.browserName) {
case 'KakaoTalk':
window.location.href = `kakaotalk://web/openExternal?url=${encodedUrl}`;
return true;
case 'LINE':
window.location.href = `line://nv/article?url=${encodedUrl}`;
return true;
case 'Naver':
window.location.href = `naversearchapp://open?url=${encodedUrl}`;
return true;
default:
// 지원되지 않는 앱 - 수동 안내 필요
return false;
}
}
window.open(targetUrl, '_blank');
return true;
};
해결책 2: Android 시스템 기본 브라우저 활용
처음에는 Chrome을 명시적으로 지정했으나, Chrome이 "사용 중지"된 경우 Intent가 조용히 실패하는 문제를 발견했습니다. 패키지를 지정하지 않고 action=android.intent.action.VIEW를 사용하면 시스템 기본 브라우저로 열립니다.
// 시스템 기본 브라우저로 열기 (Chrome 비활성화 시에도 동작)
const intentUrl = `intent://example.com#Intent;` +
`scheme=https;` +
`action=android.intent.action.VIEW;` + // Chrome 대신 시스템 기본 브라우저
`S.browser_fallback_url=${encodeURIComponent(targetUrl)};` +
`end`;
해결책 3: 인앱 브라우저 감지 시 대체 UI
인앱 브라우저가 감지되면, 기존 로그인 버튼 대신 경고 메시지와 함께 외부 브라우저로 유도하는 UI를 표시합니다.

💻 핵심 구현 코드
// LoginPage.tsx
const [inAppBrowserInfo] = useState(() => detectInAppBrowser());
return inAppBrowserInfo.isInAppBrowser ? (
// 인앱 브라우저 경고 UI
<InAppBrowserWarning
browserName={inAppBrowserInfo.browserName}
onOpenExternal={handleOpenExternal}
onCopyLink={handleCopyLink}
onGuestMode={handleGuestMode}
/>
) : (
// 일반 Google 로그인 버튼
<GoogleLoginButton onClick={handleGoogleLogin} />
);
해결책 4: 다국어 지원
강냉봇은 4개 국어를 지원하기 때문에, 인앱 브라우저 관련 메시지도 모든 언어로 번역했습니다.
// ko.json
"inAppBrowser": {
"title": "외부 브라우저에서 열어주세요",
"description": "{{browserName}}에서는 보안 정책으로 인해 Google 로그인이 제한됩니다.",
"openExternal": "외부 브라우저에서 열기",
"copyLink": "링크 복사",
"copied": "링크가 복사되었습니다!",
"manualGuide": "이 앱에서는 자동 열기가 지원되지 않습니다. 우측 상단 메뉴에서 'Safari로 열기'를 선택해주세요."
}
📈 개선 결과
| 시나리오 | 기존 | 개선 후 |
|---|---|---|
| 카카오톡 → 로그인 | ❌ 403 에러 | ✅ Safari 자동 연결 |
| Android 인앱 브라우저 | ❌ 403 에러 | ✅ Chrome 자동 연결 |
| 인스타그램/에타 (iOS) | ❌ 403 에러 | ⚠️ 수동 안내 + 복사 기능 |
| 사용자 이탈률 | 높음 | 대폭 감소 |
배운 점
- 플랫폼 제약의 이해: Google의 WebView 차단 정책은 우회할 수 없습니다. 이를 받아들이고 대안을 찾는 것이 중요합니다.
- 앱별 URL 스킴 활용: 공식 문서에 없더라도, 커뮤니티 조사를 통해 앱별 숨겨진 기능을 발견할 수 있습니다.
- Graceful Degradation: 기술적으로 완벽한 해결이 불가능할 때, 사용자에게 명확한 안내와 대안을 제공하는 것이 핵심입니다.
- 접근성 고려: 경고 UI에
role="alert", 아이콘에aria-hidden을 적용하여 스크린 리더 호환성을 높였습니다.
🚀 향후 계획: 근본적인 해결책
현재 구현은 웹 환경의 제약 내에서 최선의 우회 방법을 적용한 것입니다. 하지만 인앱 브라우저 문제를 근본적으로 해결하기 위해서는 네이티브 앱 개발이 필요합니다.
📱 React Native 도입 계획
- 네이티브 OAuth 연동: Chrome Custom Tabs (Android) / SFSafariViewController (iOS)를 통해 Google이 권장하는 방식으로 안전한 로그인 구현
- 인앱 브라우저 우회 불필요: 앱 자체에서 시스템 브라우저를 호출하므로 WebView 제한에서 완전히 자유로움
- 일관된 사용자 경험: 카카오톡, 에브리타임 등 어떤 경로로 접근해도 동일한 네이티브 로그인 플로우 제공
React Native를 통해 기존 React 코드를 최대한 재사용하면서, 네이티브 수준의 인증 경험을 제공할 예정입니다.
🛠 기술 스택 요약
| 영역 | 기술 | 활용 내용 |
|---|---|---|
| 브라우저 감지 | User-Agent 분석 | 15개+ 인앱 브라우저 패턴 감지 |
| 외부 브라우저 | URL Scheme | 카카오톡, 라인, 네이버 등 앱별 분기 |
| Android | Intent 스킴 | Chrome + Fallback URL |
| 다국어 | i18next | 4개 국어(한/영/중/일) 완벽 지원 |
| 접근성 | ARIA 속성 | role="alert", aria-hidden 적용 |
인앱 브라우저의 벽을 넘어, 모든 사용자에게 원활한 로그인 경험을 제공하기 위한 여정.
📂 프로젝트 저장소: KangNaengBot-FE
🌐 배포 URL: KangNaengBot
◆ 함께 보면 좋은 글
'프로그래밍 > 프론트엔드' 카테고리의 다른 글
| [React] 자연어 기반 시간표 자동 생성을 위한 프론트엔드 최적화 여정 (0) | 2026.01.10 |
|---|---|
| [React] AI 챗봇의 체감 속도를 높인 프론트엔드 최적화 여정 (0) | 2025.12.25 |
| [React] map() 함수를 사용한 리스트 렌더링 : key 속성으로 반복되는 컴포넌트를 만들어 보자 (0) | 2024.05.18 |
| [HTML/CSS/JS] React를 통해 useState()를 사용해 보자 : 상태를 업데이트 해보자 (0) | 2023.08.13 |
| [HTML/CSS] :root를 통해 CSS에서 변수를 사용해 보자 : 가상 클래스의 활용 (0) | 2023.06.25 |
댓글