로그인 상태로 잠시 자리를 비운 뒤 돌아와 페이지를 이동하면,헤더에서 My 버튼이 사라지거나 관리자 화면이 보기 전용으로 보이고, 새로고침해야 정상으로 돌아오는 이슈가 있었다.이번 글은 이 문제를 어떻게 진단했고, 어떤 코드로 해결했는지 정리한 내용이다.
문제 증상
로그인 상태였는데 페이지 이동 시 권한 UI가 비로그인처럼 보임
관리자 페이지가 수정 불가(보기 전용)로 렌더링됨
새로고침 후에야 관리자 권한이 다시 반영됨
원인
핵심은 서버 권한 조회 로직이었다.
기존에는 accessToken 쿠키가 없으면 /auth/me 호출 자체를 생략
그래서 BFF의 401 - refresh - retry가 트리거되지 않음
결과적으로 권한이 null로 판정되어 UI가 비정상 상태로 렌더링됨
또한 헤더 인증 상태는 최초 마운트 시 1회만 확인하고 있어서,탭 복귀/라우트 이동 시 최신 세션 상태와 동기화가 늦었다.
해결 전략
서버 권한 조회는 항상 /auth/me 호출
(accessToken 선검사 제거)
헤더 인증 상태 재동기화
라우트 변경 시
탭 포커스 복귀 시
visibilitychange가 visible이 될 때
로그인/로그아웃 직후 router.refresh()로 서버 상태 즉시 반영
핵심 코드 1) 서버 권한 조회 선검사 제거
export async function getUserRole(): Promisestring null {const res = await GET{ role?: string; user?: { role?: string }; data?: { role?: string } }("/auth/me", {
cache: "no-store",
});
if (!res.ok) return null;
return res.data.role ?? res.data.user?.role ?? res.data.data?.role ?? null;
}
export async function isAdmin(): Promiseboolean {
return (await getUserRole()) === "admin";
}
accessToken이 없더라도 /auth/me를 호출해 BFF가 refresh를 시도할 수 있게 만든 것이 포인트다.
핵심 코드 2) 헤더 인증 상태 재동기화
useEffect(() = {
let cancelled = false;
const syncAuthStatus = async () = {
try {
const res = await getCurrentUser();
if (!cancelled) setAuthStatus(!!res?.data);
} catch {
if (!cancelled) setAuthStatus(false);
}
};
const onVisibilityChange = () = {
if (document.visibilityState === "visible") {
void syncAuthStatus();
}
};
void syncAuthStatus();
window.addEventListener("focus", syncAuthStatus);
document.addEventListener("visibilitychange", onVisibilityChange);
return () = {
cancelled = true;
window.removeEventListener("focus", syncAuthStatus);
document.removeEventListener("visibilitychange", onVisibilityChange);
};
}, [pathname]);
경로 변경 + 포커스 복귀 + visible 전환 시점마다 인증 상태를 다시 맞춘다.
핵심 코드 3) 로그인 직후 서버 상태 강제 반영
showToast({
message: response.message ?? '로그인에 성공했습니다.',
variant: 'success',
duration: 2500,
});
router.replace('/');
router.refresh();
return;
로그인 후 바로 새 서버 상태(쿠키/권한)를 반영한다.
핵심 코드 4) 로그아웃 직후 서버 상태 강제 반영
try {
await logout();
showToast({ message: '로그아웃 되었습니다.', variant: 'success' });
router.replace('/login');
router.refresh();
} catch {
router.replace('/login');
router.refresh();
} finally {로그아웃 이후에도 헤더/권한 UI가 즉시 일관되도록 처리했다.
결과
자리 비운 뒤 복귀해도 권한 반영 지연이 크게 줄어듦
관리자 버튼/보기 전용 오판 빈도 감소
새로고침해야 정상화되는 UX 제거
정리
이번 이슈는 토큰 저장 위치보다 권한 판별 흐름 문제였다.
권한 체크 전에 refresh 경로를 막지 말 것
헤더 같은 전역 UI는 이벤트 기반 재동기화할 것
로그인/로그아웃 직후에는 router.refresh()로 서버 상태를 즉시 동기화할 것
BFF 인증에서는 결국 토큰보다 흐름 계약(Contract)이 더 중요했다.