[{"data":1,"prerenderedAt":16},["ShallowReactive",2],{"insight-native-to-react-native-logout":3},{"slug":4,"title":5,"description":6,"date":7,"updated":7,"category":8,"tags":9,"readingTime":14,"html":15},"native-to-react-native-logout","네이티브 앱을 React Native로 바꾸면 기존 사용자가 전부 로그아웃되는 이유","네이티브 앱을 React Native로 전환할 때 업데이트를 받은 기존 사용자가 로그인 상태를 잃습니다. Keychain·Keystore 저장 구조가 달라서 생기는 문제이고, 1회성 토큰 이관으로 막을 수 있습니다.","2026-09-04","앱 전환",[10,11,12,13],"React Native","앱 마이그레이션","Keychain","네이티브 앱 전환",9,"\u003Cp>운영 중인 네이티브 앱을 React Native로 다시 만드는 프로젝트에서 가장 늦게 발견되는 문제가 있습니다. 개발과 테스트를 모두 마치고 스토어에 업데이트를 올린 뒤에야 드러납니다. \u003Cstrong>기존 사용자가 앱을 업데이트하면 로그인이 풀립니다.\u003C/strong>\u003C/p>\n\u003Cp>새로 설치한 사용자에게는 아무 문제가 없기 때문에 개발 단계에서 잡히지 않습니다. 테스트 기기에도 대부분 새 빌드를 새로 깔아 쓰므로 재현되지 않습니다. 그래서 출시 직후 문의가 몰리고 나서야 원인을 찾게 됩니다.\u003C/p>\n\u003Ch2>왜 로그인이 풀리는가\u003C/h2>\n\u003Cp>로그인 상태는 보통 서버가 발급한 토큰을 기기에 저장해 유지합니다. 앱을 다시 켰을 때 저장된 토큰을 읽어 서버에 확인하면 다시 로그인할 필요가 없습니다. 문제는 \u003Cstrong>그 토큰을 어디에, 어떻게 저장했느냐\u003C/strong>입니다.\u003C/p>\n\u003Ch3>iOS\u003C/h3>\n\u003Cp>네이티브 앱은 보통 Keychain 에 토큰을 넣습니다. Keychain 항목에는 \u003Ccode>kSecAttrService\u003C/code>, \u003Ccode>kSecAttrAccount\u003C/code>, \u003Ccode>kSecAttrAccessGroup\u003C/code> 같은 속성이 붙고, 저장한 쪽과 읽는 쪽이 이 속성을 똑같이 맞춰야 값을 꺼낼 수 있습니다.\u003C/p>\n\u003Cp>React Native 에서 흔히 쓰는 \u003Ccode>react-native-keychain\u003C/code> 은 자체 규칙으로 \u003Ccode>service\u003C/code> 값을 정합니다. 기본값은 앱의 번들 ID 이지만, 네이티브 앱이 쓰던 service 문자열과 일치한다는 보장이 없습니다. 게다가 라이브러리가 값을 저장할 때 자체 형식으로 감싸는 경우가 있어, service 가 우연히 맞더라도 읽어낸 값을 그대로 토큰으로 쓰지 못할 수 있습니다.\u003C/p>\n\u003Cp>즉 \u003Cstrong>같은 기기의 같은 앱인데도 새 코드가 옛 토큰을 찾지 못합니다.\u003C/strong> 토큰이 사라진 것이 아니라 열쇠가 다른 것입니다.\u003C/p>\n\u003Ch3>Android\u003C/h3>\n\u003Cp>Android 는 사정이 조금 다릅니다. 네이티브 앱이 \u003Ccode>SharedPreferences\u003C/code> 나 \u003Ccode>EncryptedSharedPreferences\u003C/code> 에 저장했다면, 파일 이름과 키 이름을 알면 접근할 수 있습니다. 다만 \u003Ccode>EncryptedSharedPreferences\u003C/code> 는 Android Keystore 의 마스터 키로 암호화하므로, 그 마스터 키 별칭(alias)까지 같아야 복호화됩니다.\u003C/p>\n\u003Cp>React Native 쪽 라이브러리는 대개 자기만의 파일과 별칭을 씁니다. 결과는 iOS 와 같습니다.\u003C/p>\n\u003Ch2>웹뷰 쿠키는 또 다른 문제입니다\u003C/h2>\n\u003Cp>웹뷰를 함께 쓰는 앱이라면 문제가 하나 더 있습니다. 네이티브 앱의 웹뷰(\u003Ccode>WKWebView\u003C/code>, \u003Ccode>Android WebView\u003C/code>)에 남아 있던 세션 쿠키가 React Native 웹뷰로 자동으로 넘어가지 않습니다.\u003C/p>\n\u003Cp>특히 iOS 는 \u003Ccode>WKWebView\u003C/code> 의 쿠키 저장소가 앱의 다른 네트워크 계층(\u003Ccode>URLSession\u003C/code>)과 분리되어 있습니다. 네이티브 코드에서 로그인해 받은 세션을 웹뷰가 그대로 쓰던 구조였다면, 전환 후에는 그 연결이 끊어집니다. 앱은 로그인 상태인데 웹뷰 안 페이지만 로그아웃되어 보이는 증상이 나타납니다.\u003C/p>\n\u003Ch2>얼마나 심각한가\u003C/h2>\n\u003Cp>로그아웃 자체보다 그 뒤에 일어나는 일이 문제입니다.\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>소셜 로그인만 지원하던 앱\u003C/strong>이라면 사용자가 어떤 계정으로 가입했는지 기억하지 못합니다. 카카오로 가입했는지 애플로 가입했는지 모른 채 다른 방식으로 다시 로그인하면 계정이 갈라집니다\u003C/li>\n\u003Cli>\u003Cstrong>본인인증을 거친 서비스\u003C/strong>는 재인증을 다시 요구하게 되어 이탈이 큽니다\u003C/li>\n\u003Cli>\u003Cstrong>결제 수단이 등록된 서비스\u003C/strong>는 재로그인 과정에서 이탈하면 정기 결제가 끊깁니다\u003C/li>\n\u003Cli>스토어 리뷰에 낮은 평점이 몰리고, 이건 되돌리기 어렵습니다\u003C/li>\n\u003C/ul>\n\u003Ch2>해결 방법: 1회성 토큰 이관\u003C/h2>\n\u003Cp>원리는 단순합니다. \u003Cstrong>새 앱이 첫 실행 때 옛 저장소를 직접 읽어 새 저장소로 옮기고, 옮겼다는 표시를 남깁니다.\u003C/strong>\u003C/p>\n\u003Ch3>1단계 — 옛 저장 위치를 정확히 파악합니다\u003C/h3>\n\u003Cp>기존 네이티브 앱의 소스에서 확인해야 할 값입니다.\u003C/p>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>플랫폼\u003C/th>\n\u003Cth>확인할 것\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>iOS\u003C/td>\n\u003Ctd>Keychain 항목의 \u003Ccode>kSecAttrService\u003C/code>, \u003Ccode>kSecAttrAccount\u003C/code>, 접근 그룹(\u003Ccode>kSecAttrAccessGroup\u003C/code>), 저장한 값의 형식(문자열인지 JSON인지 아카이빙된 객체인지)\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Android\u003C/td>\n\u003Ctd>\u003Ccode>SharedPreferences\u003C/code> 파일명과 키 이름, \u003Ccode>EncryptedSharedPreferences\u003C/code> 사용 여부, 마스터 키 별칭\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\n\u003Cp>기존 앱 소스가 없다면 이 단계에서 막힙니다. 인수인계 자료에 저장소 정보가 남아 있는지 프로젝트 시작 전에 반드시 확인하십시오. \u003Cstrong>이건 개발 난이도의 문제가 아니라 정보 확보의 문제입니다.\u003C/strong>\u003C/p>\n\u003Ch3>2단계 — 네이티브 모듈로 옛 값을 읽습니다\u003C/h3>\n\u003Cp>JavaScript 만으로는 옛 Keychain 항목에 접근할 수 없습니다. iOS 는 Swift·Objective-C, Android 는 Kotlin·Java 로 작은 네이티브 모듈을 만들어 옛 속성 그대로 조회하는 함수를 노출시킵니다. 하는 일은 &quot;읽어서 문자열로 돌려주기&quot; 하나뿐이라 코드는 길지 않습니다.\u003C/p>\n\u003Ch3>3단계 — 읽은 토큰을 검증하고 새 저장소에 씁니다\u003C/h3>\n\u003Cp>읽어낸 토큰을 그대로 믿지 말고 서버에 한 번 확인하십시오. 이미 만료되었거나 서버에서 폐기된 토큰일 수 있습니다. 유효하면 새 저장소에 저장하고, 유효하지 않으면 조용히 로그인 화면으로 보냅니다.\u003C/p>\n\u003Cp>이 시점에 \u003Cstrong>리프레시 토큰으로 새 토큰을 발급받아 저장하는 방식\u003C/strong>이 더 안전합니다. 옛 토큰의 수명이 얼마 남지 않았을 수 있기 때문입니다.\u003C/p>\n\u003Ch3>4단계 — 이관 완료 표시를 남깁니다\u003C/h3>\n\u003Cp>\u003Ccode>migration_v1_done\u003C/code> 같은 표시를 새 저장소에 남겨, 두 번째 실행부터는 이관 로직을 건너뛰게 합니다. 이 표시가 없으면 앱을 켤 때마다 옛 저장소를 뒤지게 됩니다.\u003C/p>\n\u003Ch3>5단계 — 웹뷰 쿠키도 함께 옮깁니다\u003C/h3>\n\u003Cp>웹뷰를 쓰는 구조라면 이관한 토큰으로 웹뷰 쿠키를 직접 설정해 주어야 합니다. React Native 웹뷰에 쿠키를 심는 방법은 두 가지입니다. 네이티브 쪽 쿠키 저장소에 직접 넣거나, 웹뷰가 처음 로드될 때 토큰을 담은 요청을 보내 서버가 쿠키를 내려주게 하는 것입니다. 후자가 관리하기 편합니다.\u003C/p>\n\u003Ch2>검증은 이렇게 합니다\u003C/h2>\n\u003Cp>이관 코드는 \u003Cstrong>옛 앱이 설치된 상태에서 새 앱으로 덮어썼을 때\u003C/strong>만 동작합니다. 그래서 테스트도 그 조건을 그대로 만들어야 합니다.\u003C/p>\n\u003Col>\n\u003Cli>스토어에 올라가 있는 \u003Cstrong>현재 버전 앱을 설치\u003C/strong>합니다\u003C/li>\n\u003Cli>실제로 \u003Cstrong>로그인해서 토큰을 저장\u003C/strong>시킵니다\u003C/li>\n\u003Cli>그 위에 새 빌드를 \u003Cstrong>덮어씁니다\u003C/strong> (지우고 새로 깔면 재현되지 않습니다)\u003C/li>\n\u003Cli>로그인 상태가 유지되는지, 웹뷰 안쪽도 로그인 상태인지 확인합니다\u003C/li>\n\u003C/ol>\n\u003Cp>iOS 는 TestFlight, Android 는 내부 테스트 트랙으로 이 절차를 밟을 수 있습니다. 소셜 로그인 방식별로(카카오·애플·구글) 각각 확인해야 합니다.\u003C/p>\n\u003Ch2>일정에 반영해야 할 분량\u003C/h2>\n\u003Cp>전환 프로젝트 견적에서 이 항목이 누락되는 일이 많습니다. 실제로는 다음 정도가 듭니다.\u003C/p>\n\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>작업\u003C/th>\n\u003Cth>대략의 기간\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>옛 저장 구조 파악 (소스 확인 포함)\u003C/td>\n\u003Ctd>1~2일\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>iOS·Android 네이티브 모듈 작성\u003C/td>\n\u003Ctd>2~3일\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>이관 로직과 서버 검증 연결\u003C/td>\n\u003Ctd>1~2일\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>웹뷰 쿠키 이관\u003C/td>\n\u003Ctd>1~2일\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>덮어쓰기 시나리오 테스트 (로그인 방식별)\u003C/td>\n\u003Ctd>2~3일\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\n\u003Cp>기존 앱 소스가 없거나 저장 구조를 알 수 없으면 이 계획이 성립하지 않습니다. 그때는 \u003Cstrong>전 사용자에게 재로그인을 요청하는 것을 전제로\u003C/strong> 공지와 안내 화면을 설계하는 편이 낫습니다. 숨기려다 문의가 몰리는 것보다, 업데이트 화면에서 미리 알리고 재로그인 절차를 짧게 만드는 쪽이 이탈이 적습니다.\u003C/p>\n\u003Ch2>정리\u003C/h2>\n\u003Cp>네이티브 앱을 React Native로 전환할 때 로그아웃 문제는 버그가 아니라 \u003Cstrong>저장소가 바뀌면 당연히 생기는 결과\u003C/strong>입니다. 미리 알고 계획에 넣으면 대응할 수 있고, 모르고 출시하면 되돌리기 어렵습니다.\u003C/p>\n\u003Cp>전환을 검토하고 계시다면 기존 앱의 소스와 저장 구조 자료가 남아 있는지부터 확인해 보십시오. 그 자료 유무에 따라 전환 방식과 일정이 달라집니다. 저희 회사에 \u003Ca href=\"/contact\">문의\u003C/a>를 주시면 지금 가진 자료로 어디까지 이관이 가능한지 먼저 확인해 드리겠습니다.\u003C/p>\n",1788508991340]