Skip to content

로그인 기능 도입기

Leejin Yang edited this page Sep 3, 2023 · 1 revision

개요

펀잇 서비스에서 편의점 상품에 대한 리뷰와 꿀조합을 확인할 수 있습니다. 리뷰와 꿀조합은 펀잇의 핵심 기능이자 자산이고, 솔직한 리뷰와 특색있는 꿀조합을 중요하게 생각합니다. 리뷰와 꿀조합을 비회원으로 작성할 수 있다면, 리뷰와 꿀조합에서 신뢰성을 줄 수 없을 것입니다. 또한 이런 핵심 자산을 펀잇을 이용하는 회원에게 제공하고자 합니다. 그래서 펀잇은 로그인 기능을 도입했습니다.


펀잇은 카카오 로그인을 통해 서비스에 가입할 수 있습니다. 직접 사용자 인증을 하기에 받아야 하는 정보는 많았고, 이를 직접 관리 한다면 보안 측면에서도 고려해야 할 것입니다. 따라서 카카오 로그인을 통해 인증된 사용자에 대해서 서비스에 쉽게 가입할 수 있도록 했습니다.


로그인 흐름

펀잇은 세션을 통해 로그인을 처리합니다.

펀잇_데모데이_3 016

클라이언트에서 로그인 버튼을 클릭하면 /api/auth/kakao 링크로 이동합니다. 이동하게 되면 카카오 로그인 페이지로 리다이렉트 됩니다.

펀잇_데모데이_3 017

카카오 로그인을 완료하면 인가 코드를 발급하고 카카오 디벨로퍼스에 설정한 리다이렉트 URI로 이동합니다. 클라이언트에서 쿼리에 담긴 인가 코드와 함께 세션 아이디 요청을 보냅니다.

펀잇_데모데이_3 018

클라이언트에서 요청이 오면 서버에서는 카카오에 사용자 정보를 요청합니다. 카카오 측에서는 해당 사용자가 있다면 사용자 정보를 응답으로 서버에 보냅니다.

사용자 정보를 받으면 서버는 응답으로 세션 아이디가 담긴 쿠키와 회원별(기존, 신규) 리다이렉트 주소를 클라이언트에 보냅니다.

펀잇_데모데이_3 019

클라이언트에서 서버로부터 쿠키를 받으면 쿠키와 함께 서버에 사용자 정보(member) 조회 요청을 보냅니다.

서버는 응답으로 사용자 정보를 클라이언트에 보내고, 응답받은 클라이언트는 위 8번 과정에서 받은 주소로 이동합니다.


사용자 정보

응답받은 사용자 정보를 통해 클라이언트에서 로그인 유무를 판단합니다. 그렇기에 로그인을 유지하기 위해서 서비스가 refresh 되어도 사용자 정보는 유지되어야 합니다. 하지만 상태에만 저장한다면 refresh 되었을 때 사용자 정보는 초기화되고 로그인 유무를 판단할 수 없습니다. 따라서 응답으로 오면 로컬스토리지에 저장하는 방법을 선택합니다.

로컬스토리지

로컬스토리지에 데이터를 저장하면 임의로 지우지 않는 이상 브라우저에 남게 됩니다. 그렇다면 세션이 만료되었을 때도 로컬스토리지에 사용자 정보가 담겨있기에 로그인이 필요한 기능에 접근할 수 있게 됩니다.

세션이 만료되었다면 로그인 정보가 필요한 요청에 대해서 401 응답을 줍니다. 이를 이용해 아래와 같이 해결했습니다.

사용자 정보가 필요한 페이지에 접속할 때 사용자 정보 조회 요청을 보냅니다. 세션이 만료되었다면 로컬스토리지를 초기화하고 로그인 페이지로 이동합니다. 기타 로그인 정보가 필요한 요청(리뷰 조회, 작성, 꿀조합 상세 조회, 작성 등)에서도 마찬가지로 401 응답이 온다면 로컬스토리지를 초기화합니다.

로컬스토리지를 사용한다면 클라이언트 상태와 로컬스토리지의 동기화에 대한 추가적인 처리가 필요합니다. 그리고 모든 로그인 정보가 필요한 요청에서 요청을 보내고 이에 대한 예외 처리가 필요합니다. 사용자 정보 상태로만 로그인 유무를 판단할 수 있을지에 대한 생각을 해봅니다.

react query

react query의 useQuery를 사용해 사용자 정보를 조회합니다. react query에서 관리해 정보 조회 요청, 로컬스토리지 저장, 삭제를 위해 작성한 로직을 줄일 수 있습니다. 또한 유효한 세션이 필요한 요청에서 401 응답에 대한 예외 처리도 사용자 정보 유무에 따라 처리가 가능하게 됩니다. 즉, 로그인에 관한 모든 처리가 사용자 정보 조회 쿼리에서 처리할 수 있습니다.

AuthLayout

사용자 정보가 필요한 페이지를 AuthLayout으로 묶어줍니다. AuthLayout에서 사용자 정보 쿼리를 확인해 페이지를 보여주거나 로그인 페이지로 이동합니다.

각각 페이지에 흩어져 있던 조회 요청 과정은 AuthLayout에서 이루어집니다.

const AuthLayout = ({ children }: PropsWithChildren) => {
  const { data: member } = useMemberQuery();

  if (!member) {
    return <Navigate to={PATH.LOGIN} replace />;
  }

  return children;
};

// ...
{
  path: PATH.MEMBER,
  element: (
    <AuthLayout>
      <MemberPage />
    </AuthLayout>
  ),
},
// ...

마무리

로그인 기능을 구현은 문제를 만나고 해결하는 과정이 많았습니다.

fetch API에서 쿠키 담아서 요청하기, credentials 옵션 그리고 로그인 유지하는 방법 등 여러 시행착오 끝에 구현했습니다. 팀원들에게 공유하고 추후 로그인 구현을 위해 이 과정을 기록해 봅니다.

🔐 공통

🔑 프론트엔드

🔒 백엔드

📝 회의록

🤩 데모데이

Clone this wiki locally