앞선 글에서는 클라이언트에서 토큰을 어디에, 어떻게 저장해야 하는지를 다뤘습니다. 저장 위치가 정리되면, 실제 서비스에서는 다음 문제가 바로 등장합니다.
“앱을 다시 열었는데 왜 또 로그인해야 하나요?” “자동 로그인이라고 했는데, 가끔씩 풀리는 이유가 뭔가요?” “보안은 강화했는데 사용자 불만이 늘었습니다.”
로그인 상태 유지와 자동 로그인은 보안 정책과 사용자 경험이 가장 직접적으로 충돌하는 영역입니다. 여기서 중요한 것은 ‘항상 로그인 유지’가 아니라, 언제 재인증을 요구해야 하는지에 대한 명확한 기준입니다.
다음 내용을 실무 기준으로 정리합니다.
- 로그인 상태 유지와 자동 로그인 개념을 분리해서 설계하는 방법
- access token 만료, refresh 실패 시 UX 흐름 기준
- 재인증이 필요한 순간을 서버와 클라이언트가 어떻게 합의하는지
- 운영에서 자주 발생하는 혼란과 그 원인
개념/배경 설명: 자동 로그인과 로그인 유지의 차이
실무에서 자주 혼동되는 개념이 있습니다. 바로 “로그인 상태 유지”와 “자동 로그인”입니다.
- 로그인 상태 유지: 현재 세션이 유효한 동안 인증을 유지
- 자동 로그인: 앱/웹 재실행 시 사용자가 다시 입력하지 않아도 로그인 복구
이 둘을 동일하게 취급하면 문제가 생깁니다. 예를 들어 refresh token이 만료되었는데도 “자동 로그인 실패 = 서비스 오류”로 처리하면, 사용자는 이유를 이해하지 못합니다.
핵심은 다음입니다. 로그인 상태 유지는 서버 정책이고, 자동 로그인은 UX 선택입니다. 서버는 보안 기준에 따라 세션을 만료시키고, 클라이언트는 그 결과를 어떻게 보여줄지 결정합니다.
설계 1: 로그인 상태를 판단하는 단일 기준 만들기
로그인 상태 판단 기준이 여러 곳에 흩어지면, 클라이언트와 서버의 판단이 어긋나기 시작합니다.
실무에서 권장되는 단일 기준은 다음입니다.
- access token 유효 + session ACTIVE → 로그인 상태
- access token 만료 + refresh 성공 → 로그인 유지
- refresh 실패(만료/회수) → 로그인 종료
여기서 중요한 점은, access token 만료는 절대 “로그아웃”이 아니라는 것입니다. 로그아웃 판단은 refresh token과 세션 상태에서만 내려야 합니다.
실무 포인트 정리
- 로그인 여부 판단은 refresh 성공 여부를 기준으로 한다
- access token 만료는 정상 흐름이다
- 서버 기준과 클라이언트 기준을 하나로 맞춘다
설계 2: 자동 로그인 UX 흐름 설계
자동 로그인 UX는 “항상 성공해야 하는 기능”이 아닙니다. 실무에서는 다음 세 가지 상태를 명확히 나누는 것이 중요합니다.
- 무중단 복구: refresh 성공 → 사용자는 인지하지 못함
- 경고 후 복구: 추가 확인 후 refresh 재시도
- 재로그인 요구: refresh 실패 → 로그인 화면 이동
가장 나쁜 UX는 “아무 설명 없이 로그인 화면으로 이동”하는 것입니다. 사용자는 오류인지, 정책인지 알 수 없습니다.
실무에서는 다음 메시지 기준이 도움이 됩니다.
- 장시간 미사용: “보안을 위해 다시 로그인해 주세요”
- 보안 이벤트: “계정 보호를 위해 재인증이 필요합니다”
- 네트워크 오류: “연결 문제로 로그인 상태를 확인할 수 없습니다”
실무 포인트 정리
- 자동 로그인 실패 원인을 UX에서 구분해 전달한다
- 정책 실패와 시스템 오류를 같은 화면으로 처리하지 않는다
- 로그인 화면 이동은 ‘설명 후’에 한다
설계 3: 재인증이 필요한 순간 정의하기
모든 로그인 만료가 재인증을 요구해야 하는 것은 아닙니다. 재인증은 사용자에게 가장 큰 마찰을 주는 행동이기 때문입니다.
실무에서 재인증을 요구하는 대표적인 순간은 다음과 같습니다.
- refresh token 만료
- 모든 기기 로그아웃 수행
- 비밀번호 변경 또는 보안 사고 탐지
- 새로운 기기에서의 접근
반대로 access token 만료, 앱 재시작, 일시적 네트워크 오류는 재인증 사유가 아닙니다.
이 기준을 서버에서 명확히 내려주고, 클라이언트는 이를 그대로 UX에 반영하는 것이 중요합니다.
코드 예제: TypeScript 기준 인증 상태 머신
export type AuthState =
| 'AUTHENTICATED'
| 'REFRESHING'
| 'REAUTH_REQUIRED'
| 'UNAUTHENTICATED';
export interface AuthResult {
state: AuthState;
reason?: 'TOKEN_EXPIRED' | 'SESSION_REVOKED' | 'SECURITY_EVENT';
}
function resolveAuthState(result: AuthResult) {
switch (result.state) {
case 'AUTHENTICATED':
return '서비스 이용 가능';
case 'REFRESHING':
return '로그인 상태 복구 중';
case 'REAUTH_REQUIRED':
return '재로그인 필요';
default:
return '비로그인 상태';
}
}
상태를 명시적으로 정의해두면, 프론트엔드와 백엔드가 “지금 무엇을 해야 하는지”를 같은 언어로 이야기할 수 있습니다. 이는 협업 비용을 크게 줄여줍니다.
운영/실무에서 자주 겪는 문제
1. refresh 실패를 전부 오류로 처리하는 경우
정책에 의한 만료와 시스템 오류를 구분하지 않으면, 사용자는 서비스가 불안정하다고 느낍니다.
2. 자동 로그인에 대한 과도한 기대
보안을 강화할수록 자동 로그인 성공률은 낮아질 수 있습니다. 이때 UX 메시지가 없으면 불만만 남습니다.
3. 클라이언트마다 다른 로그인 기준
웹, 모바일, 어드민이 서로 다른 기준을 사용하면 CS와 운영 대응이 불가능해집니다.
실무 권장 체크리스트
- 로그인 상태 판단 기준이 서버 중심으로 정의되어 있는가
- access token 만료와 로그아웃을 구분하고 있는가
- 자동 로그인 실패 사유가 UX에 명확히 전달되는가
- 재인증이 필요한 경우만 로그인 화면으로 이동하는가
- 프론트엔드/백엔드가 동일한 상태 정의를 사용하는가
로그인 상태 유지와 자동 로그인 설계의 핵심은 “계속 로그인 상태로 둔다”가 아니라 “언제 다시 확인할지를 명확히 정한다”입니다.
재인증 기준을 명확히 하고, 그 이유를 사용자에게 설명할 수 있으면 보안과 사용자 경험은 충분히 함께 갈 수 있습니다. 결국 좋은 인증 UX는 기술이 아니라 기준에서 나옵니다.
