[TIL 2023.02.11] 소셜 로그인 (카카오톡)

김헤일리·2023년 2월 11일
1

TIL

목록 보기
26/46
post-custom-banner

서비스를 만들 때 로그인은 무조건 들어가는 요소 중에 하나다. 지금까지 로그인은 정규표현식에 맞춰서 이메일과 비밀번호 (형식만 맞는 😅) 인증이 들어갔었다. 이메일 인증까지 구현을 해볼 순 없었지만, 어쨌든 귀찮은 회원가입 절차가 필요없는 소셜 로그인 기능을 추가해봤다.

사실 소셜 로그인 3종 세트를 구현하고 싶었지만, (카카오, 네이버, 구글) 카카오를 제외한 다른 로그인 서비스들은 심사가 필요해서 너무 오래 걸릴 것 같아 일단 카카오 로그인만 구현해봤다.

내가 담당한 부분은 회원탈퇴 관련이었지만, 처음으로 구현해본 소셜 로그인 기능이다보니 전체적으로 정리를 해보려고 한다.


🔐 카카오 로그인 데이터 흐름

먼저 카카오 로그인을 구현하기 위해선 kakao developers에 들어가서 어플리케이션을 등록해야했다.
어플리케이션을 등록하면 App Key를 발급받는데, 카카오 로그인 시 Redirect URL에 client_id로 앱키를 사용하게된다.
그리고 우리가 개발하는 서비스 (localhost, product domain) 주소도 Redirect URI에 등록해주었다.

🔑 인가 코드 받기

GET /oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code HTTP/1.1
Host: kauth.kakao.com

카카오 로그인 버튼 클릭 시, GET 방식으로 해당 URL에 로그인 요청을 보낸다.
이때 client_id는 서비스의 App Key이고, redirect_URI는 서비스의 도메인 주소가 된다.

간단한 과정은 아래와 같다:

  1. 사용자가 모든 필수 동의 항목에 동의하고 [동의하고 계속하기] 버튼을 누른 경우
  2. redirect_uri로 인가 코드를 담은 쿼리 스트링 전달
  3. 사용자가 동의 화면에서 [취소] 버튼을 눌러 로그인을 취소한 경우
  4. redirect_uri로 에러 정보를 담은 쿼리 스트링 전달

로그인 과정이 완료되면 카카오측으로부터 인가 코드를 받고 해당 코드를 이용해서 로그인 과정을 실행한다.


🔓 인가 코드 받고 로그인 하기

카카오로부터 받은 코드를 백엔드쪽으로 전달하면, 백엔드 쪽에서 카카오와 소통을 여차저차 하고, 그 소통을 바탕으로 프론트쪽에 access token을 전달한다. 그러면 로그인 기능 완료!!

너무 멋진 팀원님이 만들어주신 이미지!

어떤 과정으로 데이터가 흘러가는지 아주 이해가 잘된다. 그리고... 매우 복잡한 과정으로 카카오 로그인이 진행된다는 것을 알았다. 이렇게 많은 과정이 흘러가는데, 굉장히... 빠르게 된다는 것도 신기했다.

작성한 코드:

// 카카오 로그인 인증 code
const [searchParams] = useSearchParams(); 
// 1. URL 내의 GET 디코딩 된 쿼리 매개변수에 접근
const code = searchParams.get('code'); 
// 2. 인가 코드 Redirect_URI 뒤 파라미터 ?code={코드 내용}
// 2-1. 카카오 로그인을 시도할 경우 코드를 받게되기 때문에 code의 값이 생겨난다.

// 카카오 로그인 api
const KakaoLogin = async (code) => {
// 4. code의 값이 존재할 경우 실행되는 함수.
// 4-1. 백엔드와 소통을 통해 서비스에 사용될 수 있는 access token과 refresh token을 발급받는다.
  await authAPI.KakaoLogin(code).then((response) => {
  // 4-2. code를 인자로 해서 authAPI.KakaoLogin() 함수가 실행된다.
--------------------------------------------------------------------------------------------------
// authAPI.KakaoLogin의 내용
    const KakaoLogin = async (code) => {
    // 코드의 값 = 인자
      try {
        const response = await instance.get(`auth/kakao/callback?code=${code}`);
        // 코드를 담아서 get 요청을 백엔드 서버로 전송하고, 헤당 내용을 response라는 상수에 담는다.
        return response;
        // response가 정상적으로 도착하면 response를 리턴한다.
      } catch (error) {
      // 에러가 발생할 경우, 
        useToast('에러가 발생했습니다', 'error');
        // 에러가 발생했음을 알려주는 토스트 메세지를 출력하고
      }
      return null;
      // 아무것도 리턴하지 않는다.
    };
--------------------------------------------------------------------------------------------------
  // 4-3. 무사히 소통이 완료되면, 백엔드 서버로부터 전달받은 response에 담긴 내용을 가공한다.
    setRefreshToken(response.headers.refreshtoken);
    setAccessToken(response.headers.accesstoken);
    setKakaoToken(response.headers.kakaotoken);
    // 4-4. response header에 담겨있는 인증관련 코드 3개를 전부 쿠키에 저장한다. set~~Token은 쿠키 저장 함수
    setNicknameCookie(response.data);
    // 4-5. 사용자의 닉네임은 response.data에 위치하기 때문에 알맞게 지정해서 쿠키에 저장한다.
    useToast('카카오 로그인 되었습니다.', 'success');
    // 4-6. 로그인 완료 시 완료 메세지를 출력하고,
    navigate('/');
    // 4-7. 메인 페이지로 이동시킨다.
  });
};

// 위에서 선언한 변수 code
useEffect(() => {
  if (code) {
  // 3. 만약 code의 값이 존재한다면,
    KakaoLogin(code);
    // 3-1. code를 KakaoLogin 함수의 인자로 넘긴다.
  }
}, [code]);
// 3-2. 이 useEffect는 code의 상태에 따라서 실행/재실행된다.
  • 생각보다 프론트에서 작성하는 코드의 양은 많지 않았지만, 그래도 데이터 흐름을 확실하게 알고 있어야 구현할 수 있는 서비스인 것 같다.
  • 열심히 일 해주신 팀원들께 너무너무 감사하다!!
  • 보통 서비스는 access token과 refresh token이 필요하지만, 추후 서비스 연결끊기 (회원탈퇴)를 구현하기 위해 카카오 자체에서 주는 어떠한 토큰을 kakao token으로 지정하여 따로 저장했다.

🔒 서비스 이용 에러 문제

이렇게 무사히 로그인이 완료되었지만... 몇몇 유저들은 로그인 완료 후 메인 페이지로 왔을 때, "유효한 JWT가 아닙니다!" 라는 에러 메세지가 지속적으로 생성되었다고 한다.

확인해보니 카카오 서비스 연결을 처음 했을 때 이메일을 수집하는 항목이 선택 사항이었다.
그래서 이메일 수집을 허용하지 않는 경우 로그인 시 로그인이 정상적으로 되지 않는 것이었다. 서비스 상의 오류는 아니지만, 이메일을 통해서 인증을 하는 부분이 있다고 백엔드 분 중 한명이 말씀해주셨다!

kakao developers에서 앱 설정할 때 이메일을 필수사항으로 지정할 수 없었기 때문에 선택 사항으로 지정했었다.
알아보니 필수사항으로 지정하기 위해선 추가 인증 과정이 필요했던 것!
디벨로퍼스 앱에 사업자 정보를 입력하면 된다고 한다. 꼭 사업자가 아니라도 개인 개발자로서 등록할 수 있었다! 혹시 우리 같은 문제를 겪은 분이 있다면... 링크 참고 해주세요!


🔐 소셜 로그인의 회원탈퇴와 로그아웃

회원탈퇴를 구현했을 때, 일반적으로 회원가입한 사람이라면 비밀번호를 다시 한번 입력해서 자신의 비밀번호가 지금 기입한 값과 일치할 경우 회원탈퇴 api 요청을 보내는 방식이었다.
하지만 카카오로 로그인할 경우 입력할 비밀번호가 없는 것이다!! 그리고 카카오 디벨로퍼스에서 회원탈퇴란 카카오앱 자체에서 서비스 연결을 끊어주는 것이라고 했다.

즉, 연결 끊기를 선택할 경우, 카카오앱과의 연결이 끊기는 것이고, 다시 카카오 로그인을 시도하면 초반에 서비스 연결 동의 페이지가 나온다.

여기서 문제가 있던 부분은, 카카오측에서 서비스 연결 끊기를 해줘도 서비스쪽 데이터베이스에 회원정보가 지워지지 않는 것이다. (사실 너무 당연한 것) 그래서 카카오 연결 끊는 로직과 연결이 끊겼을 때 카카오 전용 회원탈퇴 api 요청을 보내도록 코드를 구현했다.

작성한 코드:

async function onClickDeleteKakaoAccount() {
  const kakao = getKakaoToken('KakaoToken');
  // 1. 로그인 시 저장했던 카카오 전용 토큰을 바로 이곳에서 사용한다!
  const nickname = getNicknameCookie('nickname');
  // 1-1. 카카오 로그인의 경우 사용자를 구분할 수 있는 비밀번호가 없기 때문에 닉네임값을 서버에 보내기로 했다.
  const url = `https://kapi.kakao.com/v1/user/unlink`;
  // 1-2. 카카로 디벨로퍼스에 있는 연결끊기 전용 주소를 사용했다.
  const config = {
    headers: {
      Authorization: `Bearer ${kakao}`,
    },
  // 1-3. axios를 사용해서 카카오측에 요청을 보내기 때문에 요청 config에 카카오측에서 요구하는 정보를 담는다.
  // 1-4. 카카오가 요구하는 정보는 연결끊기 url과 사용자의 kakaotoken이다.
  // 1-5. 카카오토큰은 헤더에 잘 담았다 
  };
  try {
    await axios.post(url, null, config).then(() => {
    // 1-6. 먼저 axios를 사용해서 카카오측으로 서비스 연결끊기 요청을 보낸다.
      instance
        .delete(`/auth/deleteKakaoMember`, { data: { nickname } })
      // 1-7. 카카오 요청 이후 실행되는 새로운 서버 요청은 axios instance를 활용해서 서비스 백엔드로 보내는 요청이다.
      // 1-8. 이때 비밀번호 대신 사용자의 닉네임을 보냈다.
        .then(() => {
          useToast('다시 돌아올거라 믿는닭...🐓', 'success');
          removeCookie('KakaoToken');
          navigate('/');
          // 1-9. 그 이후 회원탈퇴 메세지가 출력되고, cookie를 비운 다음 메인 페이지로 보내버렸다.
        });
    });
  } catch (e) {
    useToast('회원탈퇴에 실패했닭! 다시 시도해야한닭!', 'error');
    // 1-10. 만약 에러가 발생할 경우, 안내 메세지를 출력하도록 설정했다.
  }
}

연결끊기를 시도할 때 kakaoToken 값이 처음 로그인할 때 확인되지 않아서 고생을 엄청 했었다.
access token이나 refresh token은 response.headers에 잘 담겨서 확인할 수 있었는데, kakao token의 경우 네트워크 헤더에만 표시되고 axios로 받는 응답 헤더엔 아무리 봐도 없었다!

확인해보니 access-control-expose-headers는 표시되는 토큰이 axios reponse header에 없던 이유가 따로 있었다. 정확히는 모르지만 CORS 처럼 해당 토큰을 무언가 허락(allow)하지 않았기 때문에 생기는 문제였다.
백엔드 분이 간단히 수정해주셔서 무사히 해결됐었다!



로그인쪽을 해본건 아니지만, 회원탈퇴를 구현하면서 로그인 로직과 데이터의 흐름을 파악할 수 있어서 좋았다.
다음에 사이드 프로젝트를 진행할 땐 이번에 사용하지 못 했던 네이버와 구글 로그인도 시도해보고싶다!

profile
공부하느라 녹는 중... 밖에 안 나가서 버섯 피는 중... 🍄
post-custom-banner

0개의 댓글