React - 클린업으로 엄격모드 로그인 문제 해결하기

sarang_daddy·2023년 9월 1일
0

React

목록 보기
16/26
post-thumbnail

소셜 로그인으로 구현한 로그인 로직에서 동일한 사용자로 로그인을 시도했음에도 로그인이 잘되는 경우와 에러가 발생하는 경우가 발생했다. 성공이면 성공, 에러면 에러가 나와야 하는데, 잘 되기도 하고 안되기도 하고 개발 입장에서는 원인을 파악하기 어려운 상황이었다. 다행히 그 원인을 리액트 공식문서 학습에서 엄격 모드 때문임을 알게 되었다.

😢 문제 상황

동일한 사용자로 로그인을 시도하면 성공하는 경우도 있고 실패하는 경우도 발생

👀 문제 파악하기

개발자 도구 네트워크를 확인하니 로그인 요청이 두 번 발생하고 있다.

  • 요청이 두 번 발생하기에 잘못된 인증코드가 서버로 가는 경우가 발생
  • 엄격모드는 컴포넌트의 부수효과를 두 번 호출한다.
  • 이는 상용 환경에서는 발생하지 않는 문제이지만, 엄격모드를 제거한다면 개발 단계에서 잠재적 문제를 발견하기 어렵다.
  • useEffect에 클립업 함수를 추가해서 두 번째 요청을 무시하도록 해야한다.

💡 문제 해결

로그인 로직 점검

1. 사용자에게 GitHub 로그인을 제안

  • 웹에서 "GitHub으로 로그인" 버큰 제공
  • 사용자 로그인 버튼 클릭

2. 사용자를 GitHub 인증 페이지로 리다이렉트

  • GitHub의 OAuth 인증 페이지로 리다이렉트

3. 사용자 인증 및 권한 부여

  • 사용자는 GitHub에 로그인하고 애플리케이션에 필요한 권한 부여

4. GitHub에서 웹으로 리다이렉트

  • 인증 및 권한 부여가 성공하면 GitHub는 사용자를 웹으로 다시 리다이렉트
  • 인증 코드(AUTHORIZATION_CODE)가 URL의 쿼리 파라미터로 전달

5. 서버로 AUTHORIZATION_CODE와 함께 로그인 요청

  • 리다이렉트 되면서 클라이언트에서 서버측으로 AUTHORIZATION_CODE 코드와 함께 로그인 요청
  • 서버는 AUTHORIZATION_CODE코드와 "client_secret"을 함께 GitHub OAuth 서버로 전송하여 액세스 토큰 획득

6. 액세스 토큰을 이용하여 사용자 정보 조회

  • 서버는 액세스 토큰을 이용하여 GitHub API를 통해 사용자 정보를 가져온다
  • 사용자 정보가 "신규" 회원인지 "기존회원" 인지 서버에서 판단하고 클라이언트에게 응답

7. 서버 응답으로 회원가입 or 로그인 진행

  • 서버로 부터 "응답"과 함께 JWT을 받는다
  • 응답이 "신규" 이면 JWT로 회원가입 진행
  • 응답이 "기존회원" 결과를 받으면 JWT로 로그인 진행

5번, 서버로 로그인 요청에서 디버깅

const Callback = () => {
  const searchParams = new URLSearchParams(window.location.search);
  const code = searchParams.get(AUTHORIZATION_CODE);
  const { data } = useAsync(() => postLogin(code));
  const { handleLogin } = useAuthContext();
  const navigate = useNavigate();

  useEffect(() => {
    if (data?.status === 'FORBIDDEN') {
      const { nickname, profileUrl, oauthId } = data.data;
      navigate(
        `${REGISTER}?nickname=${nickname}&profileUrl=${profileUrl}&oauthId=${oauthId}`,
      );
    }

    if (data?.status === 'OK') {
      const { jwt } = data.data;
      handleLogin(jwt);
      navigate(HOME);
    }
  }, [data]);

  return (
  // 중략
  • const { data } = useAsync(() => postLogin(code))이 원인으로 판단
function useAsync<T>(
 // 중략 

  const fetchData = async (): Promise<void> => {
    dispatch({ type: 'LOADING' });
    try {
      const response: AxiosResponse<T> = await callback();
      dispatch({ type: 'SUCCESS', data: response.data });
    } catch (e) {
      dispatch({ type: 'ERROR', error: e as AxiosError });
    }
  };

  useEffect(() => {
    if (skip) return;
    fetchData();
  }, deps);


// 중략
}
  • 두 번째 로그인 요청에서는 fetchData()가 호출되지 않도록 클립업함수를 추가한다.
  let ignore = false;

  useEffect(() => {
    if (skip) return;

    if (!ignore) {
      fetchData();
    }
    return () => {
      ignore = true;
    };
  }, deps);
  • fetchData()ignore이 false 인 경우(첫 요청)에만 호출된다.
  • 엄격모드로 useEffect가 재실행될 때는 ignore이 true가 되어 두 번째 fetchData()호출을 방지한다.

  • 엄격모드 활성화 환경에서도 한 번의 요청만 처리되도록 수정하여 문제 해결

🧐 느낀점

계속해서 Effect의 좋은 점만을 생각하면서 사용을 해온 것 같다.
리액트 학습과 프로젝트 진행을 병행해 가면서 Effect 사용의 조심성이 계속 커지고 있다.
데이터 Fetch에서 특히 많이 사용하고 있는데, 이미 발생한 네트워크 요청은 실행 취소가 안되니, 클린업 함수에 대한 이해와 필요성을 많이 고민해 봐야겠다.

profile
한 발자국, 한 걸음 느리더라도 하루하루 발전하는 삶을 살자.

0개의 댓글