치춘짱베리굿나이스

로그인 유지 본문

ClientSide/기타

로그인 유지

치춘 2023. 5. 1. 19:17

로그인 유지

작년 부스트캠프 자료로 작성했던 것인데 블로그에 옮겨오는 것을 잊어버린듯

이슈

  • 사용자 경험 측면에서, 페이지를 이동할 때마다 로그인이 해제되어 재로그인을 해야 한다면 사용자에게 불편을 초래할 수 있음
  • 단순히 로그인 정보 (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 컴포넌트 대신 모든 페이지 컴포넌트 상단에 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]);
}
  • 위에서 가진 문제점인 App의 최초 마운트를 제외하면 내부 로직이 실행되지 않는다 를 해결하기 위해 useLocation 훅과 location 객체를 사용했다
    • useLocation 은 react-router-dom에서 지원하는 훅으로, 현재 url의 정보를 담은 객체를 반환
    • 페이지 이동 등으로 url이 변경될 경우, location 의 내용물 또한 변경
  • location 객체를 useEffect 의 의존성으로 추가하면 페이지 이동 시마다 로직을 호출할 수 있다

currentUser의 유지에 관한 논의

  • 만약 사용자가 한 번도 새로고침을 누르지 않을 경우, 전역 상태값이 사라지지 않으므로 서버 측에서의 세션 만료와 관계 없이 클라이언트의 로그인 정보가 유지된다
  • 이에 따라 currentUsersetInterval 등을 통해 주기적으로 reset 시켜주거나, 주기적으로 서버에 요청을 보내 로그인 여부를 체크한다는 방안이 거론됐었음
  • 다만 어차피 마이페이지 등 유저 권한이 필요한 페이지의 경우, 페이지를 렌더링하기 위한 데이터를 받아올 때 서버에서 쿠키 만료 여부를 같이 체크하므로 클라이언트의 전역 상태값이 유지되어도 크게 상관이 없다고 느꼈다
  • 이걸로 고민하느라 머리가 매우 아팠었던 기억이...
Comments