웹 앱을 PWA로 설치해 실행하니 일반 브라우저 탭에서 로그인한 상태가 그대로 보이지 않았다. 같은 주소를 쓰는데 왜 다른 앱처럼 동작하는지 처음에는 이상했다. 실행 모드와 브라우저 정책에 따라 저장소와 쿠키가 보이는 범위, 창의 생명주기가 달라질 수 있었다.
PWA는 웹을 홈 화면에 올리는 아이콘만이 아니라 별도 실행 환경을 가진 클라이언트였다.
단일 저장소에 의존하지 않는 인증 상태
JavaScript 전역 변수나 localStorage의 값만으로 로그인 여부를 결정하지 않고 서버 세션 확인 API를 기준으로 시작 상태를 만들었다.
async function bootstrapSession() {
const response = await fetch('/api/example/session', {
credentials: 'include'
});
return response.ok ? await response.json() : null;
}
로컬 값은 화면을 빠르게 준비하는 힌트로만 사용하고 권한은 서버 응답으로 확정했다.
Start URL과 Manifest Scope 정합성
manifest의 start_url과 scope, 서비스 워커 범위가 로그인 콜백 경로를 포함하는지 확인했다. 다른 origin이나 범위 밖으로 이동하면 설치된 창이 브라우저 탭으로 열릴 수 있었다.
{
"start_url": "/app/",
"scope": "/app/",
"display": "standalone"
}
실제 경로는 배포 구조에 맞췄고 캐시된 오래된 manifest와 서비스 워커도 확인했다.
오프라인과 인증 만료의 구분
네트워크가 없는 상태에서 세션 확인이 실패했다고 즉시 로그아웃 처리하면 저장된 화면까지 잃었다. 오프라인은 인증 거절과 다른 상태로 보고 민감한 서버 작업은 막되 비민감 캐시 화면은 정책에 따라 보여줬다.
연결이 돌아오면 세션을 다시 확인하고 만료됐다면 로그인 화면으로 전환했다. 실패 원인에 따라 다음 행동이 달랐다.
설치 직후 첫 실행과 이미 열려 있던 PWA 재실행을 나눠 확인했다. 같은 URL이라도 오래된 서비스 워커가 로그인 셸을 캐시하고 있으면 새 코드가 즉시 적용되지 않을 수 있었다.
설치형 웹의 클라이언트 상태
브라우저 탭에서 되던 로그인을 그대로 포장하면 PWA에서도 될 줄 알았다. 실제로는 시작 경로, 저장소 범위, 오프라인, 업데이트된 서비스 워커가 로그인 경험에 영향을 줬다.
이 작업 뒤 PWA를 단순 배포 옵션으로 보지 않는다. 설치, 종료, 재실행, 업데이트, 오프라인이라는 생명주기를 가진 하나의 플랫폼으로 시험하게 됐다.
추가 구현 기준
이 문제를 고친 뒤 관련 공식 자료를 찾아 읽으면서, 당시 코드에서 우연히 맞았던 부분과 규칙으로 남겨야 할 부분을 나눠 봤다.
Web App Manifest는 설치 이름과 아이콘만이 아니라 start_url, scope, display를 통해 설치된 앱이 어느 경로에서 어떤 모습으로 시작할지 정의한다. 브라우저 탭에서 성공한 로그인 흐름이 홈 화면 실행에서도 같다고 가정하면 경로와 저장소 범위 차이를 놓칠 수 있다.
HTML의 form과 history 동작까지 함께 보면 설치 여부와 무관하게 새로고침·뒤로가기·외부 이동 후 복귀를 한 흐름으로 시험해야 한다.
다음에 PWA 로그인 흐름 구축 같은 문제를 보면 아래 순서부터 확인하려고 한다.
- 익명·인증 중·인증됨·만료 상태
- 재시도 가능한 오류와 사용자 입력 필요 오류
- 토큰 폐기와 화면 초기화 순서
- 동시에 도착한 만료 응답의 단일 처리
설치 가능 여부와 로그인 완료 여부를 섞지 않고, scope 밖으로 나갔을 때 일반 브라우저로 전환되는 경로를 명시해야 한다.
공식 참고 자료
- W3C Web App Manifest 초안 — start_url·scope·display 등 설치 메타데이터
- W3C HTML 5.2 — 폼·포커스·autocomplete·탐색 동작