Cheondi
개발 · 웹

PWA 로그인 흐름을 처음 붙여 보다

설치형 PWA와 일반 브라우저에서 로그인 상태가 다르게 이어지는 문제를 정리한 기록입니다.

  • #pwa
  • #login
  • #web

웹 앱을 PWA로 설치해 실행하니 일반 브라우저 탭에서 로그인한 상태가 그대로 보이지 않았다. 같은 주소를 쓰는데 왜 다른 앱처럼 동작하는지 처음에는 이상했다. 실행 모드와 브라우저 정책에 따라 저장소와 쿠키가 보이는 범위, 창의 생명주기가 달라질 수 있었다.

PWA는 웹을 홈 화면에 올리는 아이콘만이 아니라 별도 실행 환경을 가진 클라이언트였다.

인증 상태를 저장소 하나에 기대지 않았다

JavaScript 전역 변수나 localStorage의 값만으로 로그인 여부를 결정하지 않고 서버 세션 확인 API를 기준으로 시작 상태를 만들었다.

async function bootstrapSession() {
  const response = await fetch('/api/example/session', {
    credentials: 'include'
  });
  return response.ok ? await response.json() : null;
}

로컬 값은 화면을 빠르게 준비하는 힌트로만 사용하고 권한은 서버 응답으로 확정했다.

시작 URL과 범위를 맞췄다

manifest의 start_urlscope, 서비스 워커 범위가 로그인 콜백 경로를 포함하는지 확인했다. 다른 origin이나 범위 밖으로 이동하면 설치된 창이 브라우저 탭으로 열릴 수 있었다.

{
  "start_url": "/app/",
  "scope": "/app/",
  "display": "standalone"
}

실제 경로는 배포 구조에 맞췄고 캐시된 오래된 manifest와 서비스 워커도 확인했다.

오프라인과 만료를 구분했다

네트워크가 없는 상태에서 세션 확인이 실패했다고 즉시 로그아웃 처리하면 저장된 화면까지 잃었다. 오프라인은 인증 거절과 다른 상태로 보고 민감한 서버 작업은 막되 비민감 캐시 화면은 정책에 따라 보여줬다.

연결이 돌아오면 세션을 다시 확인하고 만료됐다면 로그인 화면으로 전환했다. 실패 원인에 따라 다음 행동이 달랐다.

설치 직후 첫 실행과 이미 열려 있던 PWA 재실행을 나눠 확인했다. 같은 URL이라도 오래된 서비스 워커가 로그인 셸을 캐시하고 있으면 새 코드가 즉시 적용되지 않을 수 있었다.

설치형 웹도 클라이언트 상태가 필요했다

브라우저 탭에서 되던 로그인을 그대로 포장하면 PWA에서도 될 줄 알았다. 실제로는 시작 경로, 저장소 범위, 오프라인, 업데이트된 서비스 워커가 로그인 경험에 영향을 줬다.

이 작업 뒤 PWA를 단순 배포 옵션으로 보지 않는다. 설치, 종료, 재실행, 업데이트, 오프라인이라는 생명주기를 가진 하나의 플랫폼으로 시험하게 됐다.