[오픈 API] 카카오 로그인 구현 (REST API)

windowook·2024년 7월 18일
post-thumbnail

🌱 프론트엔드 구현

팀 프로젝트에서는 로그인 페이지로 이동시켜준 뒤, 사용자가 카카오 계정에 로그인하여 서비스의 동의 항목을 모두 동의하고 나면 카카오 서버에서 클라이언트로 미리 설정한 Redirect URI에 쿼리 파라미터로 각 정보를 담아 보내고 쿼리파라미터에서 Params를 추출하여 저장하면 되었습니다. 이 과정을 이번에는 프론트엔드에서 완전히 다 처리하는 방식으로 구현해보고자 합니다.

애플리케이션 추가하기

https://developers.kakao.com/console/app

먼저 카카오 개발자 센터에서 '내 애플리케이션'으로 접속하여 애플리케이션을 추가해야합니다. 애플리케이션은 개발 중인 프로젝트와 이름을 맞춰도 되고 하고 싶은 대로 이름과 사업자명, 아이콘 파일을 설정해주면 됩니다.

좌측 카테고리의 ‘앱 설정 - 요약 정보’ 탭에 접속하면 내 애플리케이션 앱 키와 정보를 확인할 수 있습니다.
REST API키를 복사하여 환경 변수에 저장합니다.

플랫폼에서 도메인 등록

프론트는 localhost:3000 에서 확인하기 때문에 [플랫폼]에서 Web 도메인을 http://localhost:3000으로 등록해주면 해줍니다. 만약 서비스를 배포한다면 배포된 서비스의 도메인으로 변경해주면 됩니다.

그리고 [제품 설정] 카테고리 탭의 [카카오 로그인]으로 가서 활성화 설정 ON, OpenID Connect 활성화 설정을 ON으로 합니다.

보안 설정

REST API를 사용하는 방식의 경우 client secret을 필수로 활성화 해야 합니다. 프로젝트의 환경변수에 별도로 저장해둡니다.

리다이렉트

그리고 리다이렉트 URI를 설정해줍니다. 일반적으로 {기본 도메인}/auth를 경로로 지정합니다. 혹은 /auth/kakao로 설정해도 됩니다. 프로젝트 안에서 리다이렉트 URI와 같은 페이지 컴포넌트를 만들고 리액트 라우터에 path를 동일하게 설정합니다. 그리고 컴포넌트에서 인가코드 추출과 토큰 발급 요청을 위해서 로직을 구현합니다.

// index.js or App.js
{ path: '/auth', element: <GetToken /> },
// Kakao.jsx
import Button from '@mui/material/Button';
import LOGO_KAKAO from '../assets/images/logo_kakao.png';

export default function Kakao() {
  const REST_API_KEY = process.env.REACT_APP_KAKAO_REST_API_KEY; // REST API KEY가 client id
  const REDIRECT_URI = 'http://localhost:3000/auth';
  const kakaoURL = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`;
  const handleLogin = () => {
    try {
      window.location.href = kakaoURL;
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <>
      <Button
        variant="contained"
        sx={{
          mt: 3,
          gap: 1,
          backgroundColor: '#fddc3f',
          color: '#000000',
          display: 'flex',
          alignItems: 'center',
          '&:hover': {
            backgroundColor: '#ffffff',
            color: '#000000',
          },
        }}
        onClick={handleLogin}
      >
        <img src={LOGO_KAKAO} alt="카카오 로고" width="20" height="20" />
        <span>카카오 로그인</span>
      </Button>
    </>
  );
}

저는 카카오 로그인 접속을 위한 컴포넌트를 MUI에서 제공해주는 버튼 컴포넌트로 구현했습니다. 그래서 버튼을 클릭하면 우리가 익숙하게 봤던 카카오 로그인 창이 열리게 되고, 처음 로그인을 하면 서비스 이용에 필요한 약관들을 동의하는 페이지로 이동합니다.

동의하고 가입을 완료하면 위에서 설정했던 리다이렉트 URI 페이지로 리다이렉트됩니다. 리다이렉트 후에 {도메인}/auth?code={인가 코드}와 같은 형태로 쿼리스트링에 인가 코드가 붙어있습니다.

유저가 동의 과정에서 취소를 눌러도 에러 정보가 쿼리스트링에 담겨서 리다이렉트 URI로 옵니다. 초기화면과 로그인 화면이 동일하므로 유저가 동의하지 않으면 '시작 화면으로 돌아갑니다!'라는 팝업을 띄워주고 초기화면으로 이동시키는 로직으로 구현했습니다.

애플리케이션 필수 동의항목 설정하기

유저가 동의항목을 선택하는 내용을 미리 설정해놔야겠죠? 그러기 위해서는 먼저 비즈 앱 등록을 해야합니다. 비즈 앱은 개인 개발자 비즈 앱 전환을 눌러 서비스의 이름과 사진을 등록해줘야 합니다. 애플리케이션을 추가할 당시에 이미 하셨다면 이 과정은 이미 적용되어있으니 등록을 완료하시면 됩니다.

개인 개발자 비즈 앱으로 전환하고 나면 닉네임, 프로필 사진, 이메일을 동의항목으로 설정할 수 있습니다. 각 정보를 필수 수집으로 설정하고 동의 약관 문구를 적절하게 설정해줍니다. 그럼 완성된 동의항목 창은 위의 사진과 같이 나타나게 됩니다.

인가 코드로 액세스 토큰 발급 받기

accessToken과 refreshToken을 발급받기 위해서는 인가코드를 카카오 서버로 전송해야합니다. 토큰 발급을 위한 POST 요청에 담아야 할 정보는 아래와 같습니다.

요청이 성공하면 돌아오는 응답은 아래와 같습니다.

콘솔에 응답 data 본문을 출력하면 아래와 같이 JSON 형태로 오고 있음을 확인할 수 있습니다.

액세스 토큰을 이용하여 프로필 정보 조회하기

프로필 정보를 조회하는 요청의 경로는 위와 같습니다. 아래는 응답으로 오는 데이터의 속성들입니다.

로그아웃과 토큰 삭제

위의 요청은 ‘로그아웃’ 요청입니다. 카카오에는 일반 로그아웃 말고 ‘카카오 계정과 함께 로그아웃’도 있습니다. 저는 제 애플리케이션에서만 로그아웃을 시켜주면 되기 때문에 위의 방식을 사용했습니다.

실제로 로그아웃을 할 때는 로컬 스토리지나 쿠키에 저장된 값들을 지우는 로직도 포함시켜서 구현해주어야 합니다.

const handleLogout = async () => {
    try {
      const socialType = localStorage.getItem('socialType');
      const accessToken = localStorage.getItem('accessToken');

      if (socialType === 'Kakao') {
        await axios.post(
          'https://kapi.kakao.com/v1/user/logout',
          {},
          {
            headers: {
              'Content-Type': 'application/x-www-form-urlencoded',
              Authorization: `Bearer ${accessToken}`,
            },
          },
        );
      }

      localStorage.removeItem('accessToken');
      localStorage.removeItem('socialType');
      localStorage.removeItem('userId');
      localStorage.removeItem('imgUrl');
      localStorage.removeItem('nickname');
      localStorage.removeItem('activePage');
      router.push('/');
    } catch (error) {
      console.error('로그아웃 에러');
    }
  };

저는 로컬스토리지에 각 정보들을 저장하는 식으로 구현했었기 때문에 위 코드와 같이 로그아웃시에 요청을 보내고 로컬 스토리지에 저장했던 데이터들을 삭제해주는 로직을 handle 함수로 구현했었습니다.

정리

사실 소셜 로그인의 경우 프론트엔드에서 곧바로 토큰을 받고 이 값을 저장하는게 좋은 방향은 아닙니다. 백엔드가 있다면 가장 좋고, 그렇지 않다면 Supabase나 Firebase같은 BaaS의 연동을 통한 세션을 받는 형식의 관리 방식이 보안적인 측면에서 좋겠죠. 다음에 Supabase를 사용하게 된다면 연동해봐야겠습니다.

profile
안녕하세요

0개의 댓글