Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ Feature/dev 68 ] 완료 페이지로 이동 시 로그인 로직 구현 #132

Merged
merged 11 commits into from
Aug 3, 2024

Conversation

soomin9106
Copy link
Collaborator

@soomin9106 soomin9106 commented Aug 3, 2024

🔥 Related Issues

https://linear.app/fewletter/issue/DEV-68/%EC%99%84%EB%A3%8C-%ED%8E%98%EC%9D%B4%EC%A7%80%EB%A1%9C-%EC%9D%B4%EB%8F%99-%EC%8B%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84

💜 작업 내용

  • fetch interceptor 구현 - refreshToken 으로 새로운 accessToken 발급 받는 로직 작성
// refreshToken 으로 새로운 accessToken 발급 받기
const refreshAccessToken = async (): Promise<string> => {
  const refreshToken = document.cookie
    .split('; ')
    .find(row => row.startsWith('refreshToken='))
    ?.split('=')[1];

  if (!refreshToken) {
    throw new Error('No refresh token found');
  }

  // TBD: URL 이름 바꿔야 함
  const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/auth/refresh`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ refreshToken }),
  });

  if (!response.ok) {
    throw new Error('Failed to refresh access token');
  }

  const data = await response.json();
  document.cookie = `accessToken=${data.accessToken}; path=/`;
  document.cookie = `refreshToken=${data.refreshToken}; path=/`;

  return data.accessToken;
};
  • fetch interceptor 구현 - onRequest, onResponse 로직 구현
    onRequest => cookie 에 있는 accessToken 확인 + 헤더에 넣어주기
    onResponse => 401 일 경우 refreshToken 통신 하는 로직
const fetInterceptor: Interceptor = {
  onRequest: (config) => {
    const accessToken = document.cookie
      .split('; ')
      .find(row => row.startsWith('accessToken='))
      ?.split('=')[1];

    if (accessToken) {
      config.headers = {
        ...config.headers,
        'Authorization': `Bearer ${accessToken}`,
      };
    }

    return config;
  },
  onResponse: async <T extends object>(response: ApiResponse<T>) => {
    if (!response.ok && response.status === 401) {
      try {
        const newAccessToken = await refreshAccessToken();
        response.config.headers = {
          ...response.config.headers,
          'Authorization': `Bearer ${newAccessToken}`,
        };
        const retryResponse = await fetch(response.url, response.config);
        return processApiResponse<T>(retryResponse, response.config);
      } catch (error) {
        console.error('Failed to refresh token', error);
        return Promise.reject(error);
      }
    }
    return response;
  },
  onRequestError: (reason) => Promise.reject(reason),
  onResponseError: (reason) => Promise.reject(reason),
};
  • /auth/validation/complete?auth_token=~ 페이지로 진입 시 useAuth 훅을 통해 로그인 ( 혹은 회원가입 후 로그인) 수행
export const useAuth = (auth_token: string) => {
  const { mutate: postToken } = useMutation({
    ...postTokenQueryOptions(
      { auth_token },
      {
        onSuccess: (response: ApiResponse<tokenResponse>) => {
          if (response?.data?.data) {
            const { accessToken, refreshToken } = response.data.data;

            // 쿠키에 토큰 저장
            document.cookie = `accessToken=${accessToken}; path=/`;
            document.cookie = `refreshToken=${refreshToken}; path=/`;
          }
        },
        onError: (error) => {
          // 로그인 실패
          console.error("Authentication failed:", error);
        },
      },
    ),
  });

  useEffect(() => {
    if (auth_token) {
      postToken();
    }
  }, [auth_token, postToken]);
};
  • useIsLogin 커스텀 훅 개발 => 클라이언트 쪽에서도 로그인 되었는지 아닌지 알 수 있도록 함
import { useEffect, useState } from 'react';

export const useIsLogin = () => {
  const [isLogin, setIsLogin] = useState(false);

  useEffect(() => {
    const checkLogin = () => {
      const accessToken = document.cookie
        .split('; ')
        .find(row => row.startsWith('accessToken='))
        ?.split('=')[1];

      if (accessToken) {
        setIsLogin(true);
      } else {
        setIsLogin(false);
      }
    };

    checkLogin();
  }, []);

  return isLogin;
};

😡 Trouble Shooting

  • 아직 테스트 중인 코드 입니다!!

Screenshot 📷

  • API call 시 Authroization 에 Bearer 에 토큰 추가
    스크린샷 2024-08-03 오후 4 49 13

Reference 📚

@soomin9106 soomin9106 marked this pull request as draft August 3, 2024 03:55
@soomin9106 soomin9106 marked this pull request as ready for review August 3, 2024 09:41
@soomin9106
Copy link
Collaborator Author

@Happhee 한 번 확인 해주셔도 됩니당!! 🙇‍♀️

Copy link
Collaborator

@Happhee Happhee left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인 처음인데 고생하셨습니다..! 테스트 해보면서 버그 하나씩 잡아봐야할것같네욥!

const accessToken = document.cookie
.split('; ')
.find(row => row.startsWith('accessToken='))
?.split('=')[1];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 쿠키 파싱하는 유틸을 따로 만들면 원하는 key 값에 대해서 value를 따로 가져오기 편할것같은데 어떻게 생각하실까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

쿠키의 키도 상수로 관리하면 편할것같습니다!

if (typeof document !== 'undefined') {
document.cookie = `accessToken=${accessToken}; path=/`;
document.cookie = `refreshToken=${refreshToken}; path=/`;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

next에서 제공하는

import { setCookie } from "cookies-next";

를쓰지않고 직접 document에 접근하신이유가 있나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ㅎㅎㅎㅎㅎ… 까먹었었습니다 ㅋㅋㅋㅋ ☺️

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ed1a0f2

getCookie, setCookie 사용하는 방향으로 리팩토링 했습니다!

export const useIsLogin = () => {
const [isLogin, setIsLogin] = useState(false);

useEffect(() => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect(function 함수명(){

이런식으로 컨벤션 맞췄어요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

엇 네네!! 🫡🫡


useEffect(() => {
const checkLogin = () => {
if (typeof document !== 'undefined') { // 클라이언트 환경에서만 실행
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useEffect가 client 환경아니었었나욥..?그저궁금..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 저도 확인해봐야 할 것 같은데 document is not defined 에러 때문에 전체적으로 적용해놨었던 것 같네요! 저 부분은 클라 환경이 맞는 것 같네요

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@soomin9106 제가 알기론 setCookie쓰면 적용되는걸로 알고있긴합니다만..시도해보시고 말씀해주세요..!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

잘 됩니닷!! 👍

@soomin9106
Copy link
Collaborator Author

간단하게 리팩토링 완료 했습니다!
conflict 해결하고 머지할게욥 @Happhee

@soomin9106 soomin9106 merged commit ac131af into develop Aug 3, 2024
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants