Skip to content

로그인 유지

Ji Yoon Choi edited this page Dec 4, 2022 · 6 revisions

로그인 유지

이슈

  • 사용자 경험 측면에서, 페이지를 이동할 때마다 로그인이 해제되어 재로그인을 해야 한다면 사용자에게 불편을 초래할 수 있습니다.
  • 페이지별로 열람 권한을 다르게 적용해야 하는 부분이 조금씩 있어, 로그인 정보에 접근해야 했습니다.
    • 메인 페이지는 로그인이 되지 않은 사용자라도 자유롭게 접근이 가능합니다.
    • 마이페이지는 로그인 되지 않은 익명 사용자의 경우 유저 정보가 존재하지 않아 서버로부터 받아올 수 없기 때문에 접근을 막아 주어야 합니다.
    • 로그인 페이지와 회원가입 페이지는 로그인이 되어 있는 사용자가 접근할 수 없어야 합니다.
  • 위의 두 문제는 단순히 로그인 정보 (id - 다른 사람이 열람해도 상관없는 값) 를 전역 상태값으로 관리하면 해결됩니다.
  • 허나 만약 유저가 페이지 내부의 상호작용 (버튼, 링크 등) 을 거치지 않고 url로 직접 접근하거나, 새로고침을 할 경우 웹 페이지 전체가 리로드되면서 로그인 상태값이 사라져 버립니다.

해결 방안

CheckLogin 컴포넌트

export const CheckLogin = ({ children }: Props) => {
  const [currentUser, setCurrentUser] = useRecoilState(currentUserState);

  useEffect(() => {
    if (currentUser.id) return;
    fetchCheckLogin().then((data) => {
      const { data: body, code } = data;
      if (code !== 10000) setCurrentUser({ id: null });
      else setCurrentUser({ id: body.id });
    });
  }, []);

  return children;
};
  • 처음에는 CheckLogin 컴포넌트를 작성하였고, 내부에서 children prop을 이용하여 페이지를 렌더링하는 방식을 사용하였습니다.
    • 컴포넌트 내에서는 fetchCheckLogin 함수를 통해 서버로부터 로그인 여부 확인 요청을 보내고, 응답에 따라 전역 상태값으로 로그인된 유저의 id (고유값) 를 저장합니다.
    • 로그인 여부는 요청에 함께 실어 보내는 쿠키를 통해 파악합니다.
  • 이 컴포넌트는 각 페이지 접근 시마다 서버에 요청을 보내 로그인 여부를 확인 후, 로그인 정보를 다른 페이지에서 사용할 수 있도록 전역 상태값에 저장하는 것을 목표로 합니다.
  • 다만 CheckLogin은 내부에 렌더링되는 요소가 전혀 없이, 로직만 수행하는 컴포넌트이기 때문에 로직을 분리하여 커스텀 훅을 작성하는 것이 더 적합하다고 판단하였습니다.

useCheckLogin 훅

// useCheckLogin.ts
export function useCheckLogin() {
  // CheckLogin과 완전히 동일한 로직, 단 커스텀 훅으로 변경 후 children prop 삭제
}
// App.tsx
...
const App = () => {
  useCheckLogin();
  return (
    <Routes>
      ...
    </Routes>
  );
}
  • 위와 완전히 동일한 로직을 가진 useCheckLogin 훅을 만듭니다.
    • 모든 페이지의 최상위에서 동작한다는 전제는 그대로이므로, 컴포넌트 트리의 최상위 노드인 App 의 로직 부분에 훅을 호출합니다. (App.tsx)
  • 이 로직에는 근본적인 문제점이 있었습니다.
    • 해당 로직의 의도는 모든 페이지 접근 시마다 로그인 여부를 응답으로 내려받음으로써 페이지 접근 권한을 체크하고, 토큰이 만료되었다면 로그인을 즉각 해제하기 위함이었습니다.
    • 허나 처음 페이지를 열었을 때를 제외하면 App은 다시 마운트되지 않고 하위 컴포넌트만 마운트와 언마운트가 이루어지는 방식으로 페이지 이동이 구현되어 있기 때문에, useCheckLoginApp의 최초 마운트를 제외하면 두번 다시 내부 로직이 실행되지 않습니다.
    • 극단적으로 사용자가 절대로 새로고침을 누르지 않고 페이지에 머무른다면, 로그인이 무기한 유지될 수 있다는 위험이 있습니다.
    • 서버 측에서 로그인 정보가 만료되었더라도 클라이언트 측은 새로고침을 통한 App 재 마운트를 제외하고는 만료 여부를 알 수 없습니다.
  • App 컴포넌트 대신 모든 페이지 컴포넌트 상단에 useCheckLogin 을 호출하면 문제가 해결되지만, 각 페이지와 직접적으로 연관된 로직이 아니므로 최상위에서 지속적으로 동작하는 로직을 만드는 편이 관심사 분리와 코드 유지 측면에서 조금 더 어울린다고 판단하였습니다.

useLocation의 도입

export function useCheckLogin() {
  const [currentUser, setCurrentUser] = useRecoilState(currentUserState);
  const location = useLocation();

  useEffect(() => {
    if (currentUser.id) return;
    fetchCheckLogin()
      .then((data) => {
        const { data: body, code } = data;
        if (code !== 10000) setCurrentUser({ id: null });
        else setCurrentUser({ id: body.id });
        return code;
      })
  }, [location]);
}
  • useLocation 은 react-router-dom에서 지원하는 훅으로, 현재 url의 정보를 담은 객체를 반환합니다.
  • 또한 페이지 이동 등으로 url이 변경될 경우, location 의 내용물 또한 변경됩니다.
  • location 객체를 useEffect 의 의존성으로 추가하면 페이지 이동 시마다 로직을 호출할 수 있습니다.

얼리버드

프로젝트

개발일지

스프린트 계획

멘토링

데일리 스크럼

데일리 개인 회고

위클리 그룹 회고

스터디

Clone this wiki locally