소셜 로그인을 구현했는데 왜 로그인 정보를 받아오질 못하니...🤦‍♀️

·2024년 10월 4일

더취페이 프로젝트

목록 보기
9/37

소셜 로그인으로 한참 삽집을 하고 겨우겨우 해결했다. 소셜 로그인은 네이버카카오를 구현했고, 아무래도 보안적인 측면에서 프론트에서 소셜 로그인을 진행하기보다는 서버에서 소셜 로그인을 진행해주고 반환된 결과 값을 받는 것으로 해결했다. 즉, 이 게시글에서는 소셜 관련해서 데이터를 받아오는 것에 대해서만 기록될 예정이다.

데이터를 받아오는 것 뿐인데 이렇게 오래 걸렸는가...
도저히 데이터를 받아올 수 있는 방법이 떠오르지 않았다. REST API로 POST 요청을 보내는 것이 아니었기 때문에 이렇게 된 것인데 이에 대해서 삽질 순서대로 기록하고자 한다.

CORS 에러 발생

서버로 /auth POST 요청을 보내고 CORS 에러가 발생했다. 도저히 CORS가 날 상황이 아니였어서 한참을 구글링하다가 아래 게시글을 발견하게 되었다.
소셜 로그인 - 카카오 로그인 CORS 에러 발생할 때 - NextJS (frontend)

로그인 요청 보낼 때 fetch 나 axios, ajax 비동기 요청함수로 보내면 CORS 에러난다 <a>, <Link> 로 리다이렉트 시켜야함
코드 요청부터 백엔드에서 다 한다고 해도, 백엔드로 요청하는 것부터 리다이렉트로 요청해야함

비동기 요청함수로 로그인 요청을 보냈기 때문에 CORS 에러가 발생한 것이라고 한다. 왜냐하면 소셜 로그인이 진행되는 방식이 리다이렉트 방식이기 때문이다. 사용자가 소셜 로그인을 통해 인증을 받을 때, 클라이언트에서 요청을 보내는 것이 아니라 사용자를 소셜 로그인 제공자의 로그인 페이지로 리다이렉트하고, 사용자가 인증을 마친 후 다시 원래의 클라이언트 애플리케이션으로 돌아오도록 하는 방식이라 <a> 또는 <Link> 태그 사용이 필수적이었다.

여기가 이제 삽질의 시작이 된 것이다. Link로 로그인 데이터를 받아와야 했는데, 아무리 생각해도 좋은 방법이 떠오르질 않았다.

해결 방안 1

가장 먼저 생각한 방식은 GET으로 요청을 보낸 다음, 소셜 로그인이 처리 되면 POST 요청을 보내는 것이었다. 이때, POST를 어떻게? 보내야 하는가였다. POST 요청을 보낼 수 있는 trigger가 무엇인가?

그렇게 생각한 방식이 GET 요청을 보내면 서버에서 POST 요청이 자동으로 실행되는 HTML을 반환하는 것이다. 다만 이 방식이 과정이 복잡해 팀원들 일부가 반대해 실제 구현은 안 해보고 넘어갔다...

해결 방안 2

두 번째로 생각한 것은 최종 URL query에 카카오톡 accessToken을 포함해 전달하고 해당 token을 API로 전달해 로그인 정보를 받아오는 것이었다. 이 방법은 아무리 일시적이라고 해도 accessToken을 URL에 공개하는 것이 보안상 좋지 않을 것 같다고 생각해서 다른 방법을 생각해보기로 했다.

해결 방안 3

세 번째로 생각한 것은 자동로그인을 위해 refreshToken만을 이용해 재로그인하는 API를 구현해두었다. 이 API를 이용하면 의도와는 조금 다를지라도 로그인이 가능해지기 때문에 쿠키에 refreshToken을 담아 전달해주기로 했다.

드디어 방법이 결정돼서 해당 방식으로 구현했지만 해당 방안도 실패로 남게 됐는데 이에 대해서는 백엔드 이슈기 때문에 잘 알지 못한다. 결론적으로 최종 URL이 서버 도메인이다보니 쿠키에 저장을 해줘도 클라이언트의 쿠키에는 저장되지 못한다는 것이다. URL을 클라이언트로 전달해줄 순 없는지에 대해서도 안된다는 얘기만 들었다. 어떤 설정 때문이라는 것 같은데 잘 모르겠...지만 튼 실패로 남았다.

해결 방안 4

마지막으로 생각한 것은 팝업 창으로 postMessageAPI를 이용해 데이터를 전달 받는 것이다.
postMessageAPI를 이용하면 다른 도메인 내에서도 데이터를 전달받을 수 있다고 해서 시도해봤다.

window.open(
      `${process.env.NEXT_PUBLIC_BASE_URL}/oauth/signup?type=${type}`,
      `${type} 회원가입`,
      'width=600,height=400'
);

Link 대신 window.open 함수를 이용해 요청을 전달해주었다.

  useEffect(() => {
    const handleMessage = (event) => {
      const allowedOrigins = [process.env.NEXT_PUBLIC_BASE_URL];

      if (allowedOrigins.includes(event.origin)) {
        const userInfo = {
          userId: event.data.userId,
          nickname: event.data.nickname,
          profileImage: event.data.profileImg,
          location: event.data.location,
          isCertified: event.data.isCertified,
        };

        localStorage.setItem('loginType', event.data.loginType || 'email');
        dispatch(
          login({
            user: userInfo,
            access: event.data.access,
          })
        );

        cookies.set('refresh', event.data.refresh, { path: '/' });

        router.push('/');
        console.log(userInfo);
      }
    };

    window.addEventListener('message', handleMessage);

    return () => {
      window.removeEventListener('message', handleMessage);
    };
  }, []);

message를 전달받기 위해 이벤트 핸들러를 등록해주었다. 허용할 origins를 배열에 담아두고, 해당 origins로부터 메시지가 온다면, event.data들을 저장해주어야 하는 위치에 저장을 해주었다. 서버에서도 반환되는 페이지에 아래와 유사하게 코드를 작성해 데이터들을 전달해주었다. 여기서 부모 도메인을 하나 또는 전부로 설정할 수 밖에 없다는 건 아쉬웠다.

String script = "window.opener.postMessage('데이터 내용', 'http://부모_도메인.com');";
response.getWriter().write(script);

아쉽게도 이 방법은 개발자도구에서도 script를 보면 바로 모든 데이터들이 표시가 된다. 일시적으로 사라지는 데이터이긴 하지만... 누구나 해당 데이터를 볼 수 있게 구현하는 건 좋지 않은 것 같아서 암호화해서 데이터를 전달하기로 했다. 해당 내용에 대해서는 다른 기능을 구현하면서 암호화/복호화를 하기 때문에 그 때 정리하는 걸로 하겠다.


소셜 로그인 구현을 보면 다들 쉽게 구현하는 것 같은데 물론 구현은 쉽다. 데이터를 주고 받는 게 왜이리 어려웠는지 모르겠다... 다들 어떻게 구현했는지 이에 대해서 자세하게 알려주는 레퍼런스 찾기도 정말정말 힘들었다. 너무 힘들게 돌고돌고돌아온 건 아닐까 걱정은 된다. 유저 기능이 항상 제일 어렵긴 하지만... 이번 유저 기능이 유독 힘들었다. 흑흑 얼른 다른 기능 구현하고 싶다. 유저 기능 넘 실어잉

profile
Frontend🍓

0개의 댓글