React Native와 WebView의 인증 정보 소유권: token과 session을 왜 나누는가
네이티브 API용 소지자 토큰을 WebView에 주입해 페이지 script가 읽는 실패를 재현하고, 앱 token·서버 교환·WebView session cookie의 소유권을 분리한다.
목차
표준·구현·측정·해석 표시는 무엇인가요?
- 표준웹 표준이나 언어 명세가 정한 동작
- 구현특정 기술이나 브라우저가 실제로 구현한 동작
- 측정명시한 환경에서 직접 실행해 관찰한 결과
- 해석앞선 근거에서 도출한 설계 판단
- 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용
30초 요약
같은 사용자의 로그인 상태라도 네이티브 API access token과 WebView의 web session을 같은 값으로 만들 필요는 없다.
React Native 앱 네이티브 API용 token 보유·API 요청에만 사용
인증 backend 앱 자격 확인·더 좁은 WebView session 생성
WebView cookie 저장소 적용되는 웹 요청에 session cookie 전송
페이지 JavaScript 로그인 결과 사용·raw native token과 HttpOnly cookie는 읽지 않음반복해서 기억할 문장은 이것이다.
로그인 상태를 이어 주되, 원래 인증 자격 정보의 복사본을 실행 영역마다 늘리지 않는다.
이 글은 누가 어떤 값을 가져야 하는지만 다룬다. 네이티브 token을 장기 저장하는 플랫폼 보안 저장소는 18편, cookie가 사라진 뒤 web session을 복구하는 절차는 19편에서 이어 간다.
이 글을 관통하는 상황: 페이지가 네이티브 API token을 읽었다
앱은 네이티브 API 요청에 쓰는 access token을 이미 가지고 있다. WebView에서도 사용자를 로그인시키려는
개발자가 같은 token을 injectedJavaScriptObject로 페이지에 넣었다고 하자.
이 실험의 demo-native-access-token-not-a-secret은 의도적으로 권한이 없는 고정 문자열이다. 실제 token,
cookie, 사용자 정보는 코드·화면·log에 넣지 않는다.
16편에서 Zod 4.4.3을 설치한 WebBoundaryApp의 App.tsx를 다음 코드로 교체한다.
import React, { useState } from 'react'
import { SafeAreaView, StyleSheet, Text } from 'react-native'
import { WebView } from 'react-native-webview'
import * as z from 'zod'
const EXPOSE_NATIVE_TOKEN = true
const DEMO_NATIVE_TOKEN = 'demo-native-access-token-not-a-secret'
const CredentialAuditSchema = z.strictObject({
type: z.literal('credential.audit'),
pageCanReadNativeToken: z.boolean(),
serverSessionMode: z.boolean(),
})
type CredentialAudit = z.infer<typeof CredentialAuditSchema>
const PAGE_HTML = `<!doctype html>
<html lang="ko">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<body>
<p id="token">페이지 token 읽기: 확인 중</p>
<p id="mode">페이지 mode: 확인 중</p>
<script>
const raw = window.ReactNativeWebView.injectedObjectJson()
const initial = raw ? JSON.parse(raw) : {}
const pageCanReadNativeToken =
typeof initial.nativeAccessToken === 'string'
const serverSessionMode = initial.fixtureMode === 'server-session'
document.querySelector('#token').textContent =
'페이지 token 읽기: ' +
(pageCanReadNativeToken ? '가능' : '불가능')
document.querySelector('#mode').textContent =
'페이지 mode: ' +
(serverSessionMode ? 'server-session' : 'native-token')
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'credential.audit',
pageCanReadNativeToken,
serverSessionMode,
}))
</script>
</body>
</html>`
export default function App() {
const [audit, setAudit] = useState<CredentialAudit | null>(null)
const [status, setStatus] = useState('페이지 감사 대기')
const initialPageValue = EXPOSE_NATIVE_TOKEN
? { nativeAccessToken: DEMO_NATIVE_TOKEN }
: { fixtureMode: 'server-session' }
return (
<SafeAreaView style={styles.screen}>
<Text>상태: {status}</Text>
<Text>
페이지가 native token 읽음:{' '}
{audit === null
? '확인 전'
: audit.pageCanReadNativeToken
? '예'
: '아니오'}
</Text>
<Text>
server-session mode:{' '}
{audit?.serverSessionMode ? '예' : '아니오'}
</Text>
<WebView
key={EXPOSE_NATIVE_TOKEN ? 'native-token' : 'server-session'}
originWhitelist={['*']} // navigation 없는 인라인 fixture 전용
source={{ html: PAGE_HTML }}
injectedJavaScriptObject={initialPageValue}
onMessage={(event) => {
let input: unknown
try {
input = JSON.parse(event.nativeEvent.data)
} catch {
setStatus('감사 JSON 해석 오류')
return
}
const parsed = CredentialAuditSchema.safeParse(input)
if (!parsed.success) {
setStatus('감사 메시지 계약 오류')
return
}
setAudit(parsed.data)
setStatus('페이지 감사 완료')
}}
style={styles.webview}
/>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
screen: { flex: 1, padding: 16, gap: 12 },
webview: { flex: 1, borderWidth: 1 },
})실행 전에 예측한다.
- React Native가 주입한 token은 page script에서 읽을 수 없을까?
- token을 화면에 직접 출력하지 않아도
pageCanReadNativeToken은 무엇을 보여 줄까? - 이 page가 같은 token으로 네이티브 API를 호출할 권한까지 갖게 될 수 있을까?
상태: 페이지 감사 완료
페이지가 native token 읽음: 예
server-session mode: 아니오
웹 문서
페이지 token 읽기: 가능
페이지 mode: native-tokenfixture는 실제 token 값을 화면이나 응답에 다시 보내지 않고 읽을 수 있는지만 보고한다. 하지만 “읽을 수 있음” 자체로 소유권 경계가 넓어졌다는 사실은 충분하다. 실제 소지자 토큰이라면 값을 읽은 page script도 연결된 권한을 사용할 수 있다.
로그인 상태와 인증 정보의 복사본을 구분한다
앱과 WebView가 같은 사용자 상태를 보여 주는 것과 같은 자격 정보 문자열을 공유하는 것은 다른 요구다.
필요한 결과 WebView 서버가 사용자를 로그인 상태로 인식
잘못된 지름길 네이티브 access token 원문을 page JavaScript에 복사
소유권 분리 backend가 앱 인증을 확인하고 별도 web session을 발급이는 모든 서비스가 반드시 같은 endpoint와 이름을 써야 한다는 표준이 아니다. 핵심 불변식은 page가 로그인 결과를 사용할 수 있어도 원래 native token을 소유하지 않는다는 점이다.
주입 API를 비밀 저장소로 보지 않는다
injectedJavaScriptObject는 페이지 초기 설정, 기능 flag처럼 page script가 읽어야 할 값을 전달하는
API다. 읽어야 작동하는 API에 “페이지에는 보이지 않을 비밀”을 넣는 것은 목적과 모순된다.
다음 위치도 native token 복사본을 늘리는 경로다.
- HTML 문자열과
injectJavaScript코드 안에 token 삽입 - URL query에 access token 추가
- WebView page의
localStorage에 native token 저장 - page와 앱 message에 raw token 왕복
- 오류 log와 분석 event에 token 기록
RFC 6750은 bearer access token을 URL query로 보내는 방법이 log에 남을 가능성이 높아 권장하지 않는다. 페이지가 token을 “한 번만” 읽는다고 해도 script 실행 영역에 노출한 사실은 사라지지 않는다.
웹 session은 cookie 저장소와 서버가 소유한다
Secure는 cookie를 안전한 연결에서만 보내도록 제한한다. HttpOnly는 document.cookie 같은 page
script용 API에 cookie를 제공하지 않도록 한다. 두 속성은 서로 다른 보호이며 함께 사용할 수 있다.
Domain은 cookie를 보낼 서버 주소 범위이고, Path는 그 서버 안에서 cookie를 보낼 URL 경로 범위다.
실제 제품의 한 가지 교환 흐름은 다음과 같다.
1. React Native 앱 → 인증 backend
native token을 Authorization header로 보내 WebView session 시작 요청
2. backend
token의 사용자·대상·만료 확인
짧게 살고 한 번만 쓸 수 있는 session 시작값 발급
3. WebView → HTTPS session 시작 URL
backend가 시작값을 한 번 사용하고 폐기
Set-Cookie: web_session=...; Secure; HttpOnly; Path=/
4. WebView cookie 저장소
적용되는 후속 요청에 Cookie header 자동 전송
5. page JavaScript
서버 응답으로 로그인 결과 사용
native token과 HttpOnly cookie 원문은 읽지 않음여기서 session 시작값의 전달 위치, 수명, 한 번 사용 보장과 재사용 방지는 제품 위협 모델에 맞춰 설계해야 한다. 위협 모델은 누가 어떤 값을 노릴 수 있고 어떤 유출·재사용 경로를 막을지 정한 보안 가정이다. access token을 URL에 넣는 방식으로 대체하지 않는다. 이 글의 정적 fixture는 실제 cookie 발급을 증명하지 않으므로, 실제 backend 통합에서는 HTTP 응답과 다음 WebView 요청을 별도로 관찰해야 한다.
실패 코드를 소유권 모델로 교체한다
첫 실패를 확인한 뒤 EXPOSE_NATIVE_TOKEN을 false로 바꾼다. 실제 제품 session을 흉내 내는 인증
구현이 아니라, 페이지에 native token을 넘기지 않는 소유권 차이를 확인하는 fixture다.
const EXPOSE_NATIVE_TOKEN = falseReload 또는 앱 재시작 뒤 다음을 확인한다.
상태: 페이지 감사 완료
페이지가 native token 읽음: 아니오
server-session mode: 예
웹 문서
페이지 token 읽기: 불가능
페이지 mode: server-sessionfixtureMode는 권한이 없는 일반 문자열일 뿐 실제 web session의 성공 증거가 아니다. 실제 성공은 다음
세 지점에서 닫는다.
| 관찰 지점 | 확인할 증거 |
|---|---|
| native → backend | 올바른 API 대상에 Authorization header 사용, URL·log에는 token 없음 |
| backend → WebView | HTTPS 응답이 범위가 좁은 Secure·HttpOnly session cookie 발급 |
| WebView → backend | 다음 적용 요청에 cookie가 실리고 서버가 session 사용자·권한 확인 |
OAuth 로그인과 제품 WebView session을 합치지 않는다
이 글의 web session 시작 흐름은 이미 인증된 앱과 자기 서비스 backend가 제품 WebView session을 연결하는 모델이다. 제3자 OAuth 제공자의 로그인 form을 WebView 안에서 열고 사용자의 비밀번호를 입력받으라는 뜻이 아니다. OAuth 로그인은 RFC 8252의 외부 user-agent와 callback 경계를 따른다.
구조 검증·출처·소유권은 서로 다른 질문이다
구조 검증 message의 type·필드·값 종류가 맞는가
출처 판단 허용한 WebView 문서와 navigation인가
소유권 판단 어떤 실행 영역이 raw credential을 가져도 되는가
권한 확인 서버가 현재 사용자에게 동작을 허용하는가16편의 스키마를 통과한 nativeAccessToken: string도 소유권이 잘못된 입력일 수 있다. 반대로 page가
native token을 읽지 못한다는 사실만으로 backend session이 올바르거나 사용 권한이 있다는 뜻도 아니다.
각 질문을 별도 증거로 닫는다.
iOS와 Android에서 무엇을 각각 확인할까
| 관찰 | Android emulator | iOS Simulator | 실기기 조건 |
|---|---|---|---|
| bad fixture에서 page가 주입값 읽음 | 확인 | 확인 | 불필요 |
| good fixture에서 native token 없음 | 확인 | 확인 | 불필요 |
| 실제 cookie 저장·다음 요청 전송 | 해당 WebView 통합 확인 | 해당 WebView 통합 확인 | 보통 불필요 |
| 생체 인증·기기 보안 저장소 결합 | emulator 제공 범위 | Simulator 제공 범위 | 실제 보안 능력이 판단 대상이면 필요 |
Apple의 WebKit cookie 저장소와 Android의 WebView cookie manager는 별도 플랫폼 구현이다. 한쪽에서 cookie가 이어졌다는 사실로 다른 쪽의 저장·삭제·공유 설정을 대신 확인하지 않는다.
pull request에서 달라져야 할 행동
React Native와 WebView 인증을 연결할 때 먼저 소유권 표를 적는다.
자격 정보 누가 만들고 누가 원문을 읽는가
native token 대상 API, 수명, 메모리·저장 위치
session 시작값 한 번 사용, 짧은 수명, 재사용 거절
web session 서버 상태와 cookie 식별값의 범위
cookie 속성 Secure, HttpOnly, Domain, Path, 만료
page JavaScript 필요한 로그인 결과와 금지할 raw 값
logout native token 폐기와 web session 폐기의 순서
navigation 허용할 웹 출처와 다른 문서 이동 처리
관찰 token 없는 log, Set-Cookie, 다음 Cookie 요청
플랫폼 검증 Apple WebKit / Android WebView 각각 확인검토자는 다음을 확인한다.
- native access token과 새 access token 발급에 쓰는 refresh token을 page JavaScript에 주입하거나 message로 보내지 않는가?
- URL, HTML, localStorage와 log에 bearer token 복사본이 생기지 않는가?
- backend가 native 인증을 확인하고 더 좁은 web session을 발급하는가?
- session cookie에 HTTPS·Secure·HttpOnly와 의도한 서버 주소(host)·URL 경로(path)·만료 범위가 있는가?
- 실제 WebView 다음 요청과 서버 session 확인까지 관찰했는가?
- 구조 검증, origin, credential 소유권과 서버 권한을 서로 대신하지 않는가?
- OAuth authorization UI를 embedded WebView session 시작과 합치지 않는가?
한 장으로 다시 보기
문제 page script가 네이티브 API token을 읽을 수 있음
오판 같은 로그인 사용자는 같은 credential 문자열을 공유해야 함
관찰 injectedObjectJson에서 nativeAccessToken 접근 가능
원인 로그인 상태 전달과 raw credential 복사를 구분하지 않음
행동 앱 token → backend 확인 → 별도 web session → WebView cookie
검증 page에는 native token 없음 + 실제 다음 HTTP 요청은 session 성공
경계 스키마·origin·cookie 존재는 서버 권한을 각각 대신하지 않음인증 연결의 목표는 모든 실행 영역에 같은 token을 복사하는 것이 아니다. 앱, backend, WebView cookie 저장소와 page script가 필요한 최소 권한만 갖도록 값을 나누고, 원래 native credential은 대상 API 경계 밖으로 넓히지 않아야 한다.
스스로 확인할 질문
- 소지자 토큰을 WebView page에 복사하면 권한 범위가 어떻게 달라지는가?
- 로그인 상태 공유와 같은 credential 문자열 공유는 왜 다른 요구인가?
- Secure·HttpOnly web session cookie에서 WebView와 page script의 책임은 어떻게 다른가?
- 정적 fixture의
server-session mode가 실제 cookie 성공을 증명하지 않는 이유는 무엇인가? - 네이티브 앱의 OAuth 로그인 화면을 제품 WebView session 시작과 분리해야 하는 이유는 무엇인가?
Active recall
기억에서 꺼내 보기
답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.
01네이티브 API에서 쓰는 access token이 있으므로 같은 값을 WebView 페이지에 주입해 웹 로그인에도 써도 될까?
정답
이 글의 권장 소유권 모델에서는 안 된다. 앱은 네이티브 API용 token을 보유하고, 서버가 더 좁은 웹 session으로 교환하며, WebView는 session cookie를 저장·전송하고 페이지 script는 원래 token을 읽지 않는다.
관련 설명 다시 읽기왜 그런가
편의를 위한 복사는 WebView script까지 네이티브 token의 권한을 넓힌다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
02WebView가 로그인 cookie를 사용한다면 페이지 JavaScript도 그 값을 읽어 직접 요청에 넣어야 할까?
정답
아니다. 서버가 Secure·HttpOnly session cookie를 발급하면 WebView의 cookie 저장소가 적용되는 HTTP 요청에 보내고, HttpOnly는 script용 API에서 값을 숨긴다.
관련 설명 다시 읽기왜 그런가
웹 session 사용과 page script의 raw cookie 접근은 같은 책임이 아니다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
03injectedJavaScriptObject는 앱이 넣은 값이므로 access token을 전달해도 페이지 밖에는 노출되지 않을까?
정답
아니다. 직접 불러온 문서의 page script가 injectedObjectJson으로 값을 읽는다. 이 API는 페이지 초기 설정 전달 기능이지 자격 정보를 비밀로 보관하는 경계가 아니다.
관련 설명 다시 읽기왜 그런가
앱이 값을 만들었다는 사실은 값을 받은 JavaScript 실행 영역의 접근을 막지 않는다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
04WebView에 web session이 필요하면 OAuth 로그인 화면도 같은 embedded WebView 안에서 열어도 될까?
정답
일반화할 수 없다. RFC 8252의 네이티브 앱 OAuth authorization 요청은 embedded user-agent를 사용하지 말고 외부 user-agent를 사용해야 한다. 로그인 후 제품 WebView session을 만드는 절차와 OAuth authorization 화면은 분리한다.
관련 설명 다시 읽기왜 그런가
이 글의 서버 session 교환은 OAuth 로그인 UI를 WebView에 넣으라는 뜻이 아니다.
선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.
출처와 검증 범위
아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.
- RFC 6750 - OAuth 2.0 Bearer Token UsageInternet Engineering Task Force · RFC · 확인 2026-08-09
- RFC 8252 - OAuth 2.0 for Native AppsInternet Engineering Task Force · RFC · 확인 2026-08-09
- RFC 6265 - HTTP State Management MechanismInternet Engineering Task Force · RFC · 확인 2026-08-09
- React Native WebView 14.0.1 ReferenceReact Native WebView · 공식 문서 · 확인 2026-08-09
- React Native WebView 14.0.1 Apple injected object implementationReact Native WebView · 소스 코드 · 확인 2026-08-09
- React Native WebView 14.0.1 Android injected object implementationReact Native WebView · 소스 코드 · 확인 2026-08-09
- WKHTTPCookieStoreApple Developer · 공식 문서 · 확인 2026-08-09
- CookieManagerAndroid Developers · 공식 문서 · 확인 2026-08-09
- Zod 4 - Basic usageZod · 공식 문서 · 확인 2026-08-09
- Fast RefreshReact Native · 공식 문서 · 확인 2026-08-09
- Debugging — Accessing the Dev MenuReact Native · 공식 문서 · 확인 2026-08-09
- Extended controls, settings, and helpAndroid Developers · 공식 문서 · 확인 2026-08-09
- Running your app on simulated or physical devicesApple Developer · 공식 문서 · 확인 2026-08-09