카카오 소셜로그인 (with React, Express)

긴가민가·2023년 4월 14일
post-thumbnail

개발환경 구성을 했으니, 카카오 소셜 로그인을 구현해봅시다!

💡 소셜 로그인 개발환경 구성하기를 읽고 오시면 더 이해가 편하실거예요. :)


Kakao Developer

Kakao Developer에 로그인을 하고, 앱을 등록해주세요.

등록한 애플리케이션에 들어가면, 앱 키가 있습니다.
여기서 우리는 REST API 키를 사용할거예요.

Redirect URI 등록

이 부분을 작성해야 추후 로그인했을 때, 등록해둔 URI로 Kakao쪽에서 redirect를 수행합니다.
프론트엔드로 redirect가 발생할 수 있도록 적어주세요.

그리고 위 이미지 표시처럼 활성화 설정을 꼭 ON 하셔야합니다.

동의 항목

카카오 최초 로그인 시에 사용자 정보를 제공하는 동의 권한을 설정할 수 있습니다.
테스트니 닉네임필수 동의로 할게요.😊


구현

1. [백엔드] Kakao URL을 만들어 전달하는 API 만들기

Kakao로부터 인가코드를 받아올 URL을 백엔드 서버에서 만들어 클라이언트에게 API로 제공합니다.

env

그 전에 먼저, 프로젝트 루트에 .env 파일을 생성합니다.
.env파일은 보안과 관련된 값을 관리합니다.

// .env

KAKAO_KEY=[YOUR KAKAO REST API KEY]

Kakao util class

kakao.js에 Kakao와 관련된 기능들을 모듈화하는 클래스를 만듭니다.

// src/kakao.js

class Kakao {
  constructor() {
    this.key = process.env.KAKAO_KEY;
    this.redirectUri = `http://localhost:3000/callback/kakao`;
  }

  /**
   * @description 카카오 인가코드를 받기위한 URL 가져오기
   */
  getAuthCodeURL() {
    return `https://kauth.kakao.com/oauth/authorize?client_id=${this.key}&redirect_uri=${this.redirectUri}&response_type=code`;
  }
}

export const KakaoClient = new Kakao();

API 작성

이제 app.js 파일에 API를 만듭니다.

// src/app.js

...생략
import Kakao from "./kakao.js";

...생략

app.get("/kakao/url", (req, res, next) => {
  console.log("/kakao/url start");

  const url = KakaoClient.getAuthCodeURL();

  res.status(200).json({
    url,
  });
  
  console.log("/kakao/url finish");
});

...생략

프론트엔드에서는 해당 API를 호출하여, 받은 url로 카카오 로그인 페이지를 보여줄거예요.😊

💡 필자는 Client ID를 프론트엔드에서 관리하지 않고, 백엔드에서만 관리하도록 하기위해 이런 구성으로 진행해요. :)

💡 Kakao developer - 인가코드 받기에서 자세한 설명을 볼 수 있어요. :)

2. [프론트엔드] API 호출하여 받은 URL로 페이지 띄우기

방금 만든 백엔드 API를 프론트엔드에서 호출합니다.
로그인 버튼을 만들고 클릭 시 API를 호출하도록 구현할게요.😊

버튼 컴포넌트인 KakaoButton.js를 만듭니다.

// src/KakaoButton.js

function KakaoButton() {
  /**
   * @description URL 가져오기
   */
  const fetchGetURL = async () => {
    try {
      const { url } = await (
        await fetch("http://localhost:3001/kakao/url")
      ).json();

      console.log(url); // 응답으로 온 url
    } catch (error) {
      alert("Function fetchGetURL error!");
      console.error(error);
    }
  };

  return (
    <button className="kakao" onClick={fetchGetURL}>
      카카오 로그인하기
    </button>
  );
}

export default KakaoButton;

App.js에서 KakaoButton 컴포넌트를 렌더합니다.

// src/App.js

import "./App.css";
import KakaoButton from "./KakaoButton";

function App() {
  return (
    <div className="App">
      <KakaoButton /> {/* 버튼 컴포넌트 */}
    </div>
  );
}

export default App;

App.css에서 버튼 스타일을 추가합니다.

/* src/App.css */

...기존 코드 생략

.kakao {
  background-color: #fee500;
  color: #000000;
}

.kakao:hover {
  background-color: #f4dd0a;
}

.kakao:active {
  background-color: #ead514;
}

이제 버튼을 누르면 개발자 도구에 API 응답이 로그로 남습니다!!

url 페이지로 이동

응답받은 url로 페이지를 이동시킵니다.

KakaoButton.jsfetchGetUrl 함수를 조금 수정합니다.

// src/KakaoButton.js

...생략

  const fetchGetURL = async () => {
    try {
      const { url } = await (
        await fetch("http://localhost:3001/kakao/url")
      ).json();

      console.log(url);

      document.location.href = url; // 👈 이 부분을 추가로 넣어주세요. (페이지 이동)
    } catch (error) {
      alert("Function fetchGetURL error!");
      console.error(error);
    }
  };

...생략

다시 버튼을 눌러봅시다!

Kakao Developer에서 설정한대로 닉네임을 필수로 카카오 권한 동의를 받는 화면이 나왔어요!😎

전체 동의하고 계속 진행해볼게요.

설정한 redirect_uri의 주소로 query parameter와 함께 전달되었습니다.

query parameter의 code가 Kakao에서 발급해준 인가코드입니다.
인가코드를 백엔드에 보내야하기에, 백엔드에 API를 하나 만들겠습니다.

3. [백엔드] 인가코드를 받아 Kakao 로그인을 진행하는 API 만들기

클라이언트(프론트엔드)로부터 인가코드를 받아 Kakao 로그인 처리를 하겠습니다.

일단 Kakao로 부터 인가코드를 통해 token을 발급받아야 합니다.
발급받은 토큰으로 Kakao의 API를 사용할 수 있습니다.

토큰 발급받기

설명이 아주 자세히 되어있군요.🤩

💡 Kakao developer - 토큰 받기에서 자세한 설명을 볼 수 있어요. :)

이대로 구현해보겠습니다!

Kakao.js파일에 token을 받아오는 메소드를 추가합니다.

// src/kakao.js

import axios from "axios";

class Kakao {
  ...생략

  /**
   * @description 토큰 발급하기
   * @param code 인가코드
   */
  async getToken(code) {
    const params = {
      client_id: this.key,
      code,
      grant_type: "authorization_code",
      redirect_uri: this.redirectUri,
    }; // 👈 필수 parameter만 작성

    const { data } = await axios.post(
      "https://kauth.kakao.com/oauth/token",
      params,
      {
        headers: {
          "Content-Type": "application/x-www-form-urlencoded",
        }, // 👈 헤더 설정
      }
    );
    
    console.log(data);

    const tokenData = {
      access_token: data.access_token,
      refresh_token: data.refresh_token,
    };    

    return tokenData;
  }

...생략

사용자 정보 가져오기

받아온 인증 토큰을 헤더에 설정하고 요청하면, 로그인한 사용자의 정보를 받을 수 있는 API입니다.

💡 Kakao developer - 사용자 정보 가져오기에서 자세한 설명을 볼 수 있어요. :)

이것도 구현해보겠습니다!

// src/kakao.js

class Kakao {
  ...생략

  /**
   * @description 유저 정보 가져오기
   * @param token 액세스 토큰
   */
  async getUserData(token) {
    const { data } = await axios.get("https://kapi.kakao.com/v2/user/me", {
      headers: {
        Authorization: `Bearer ${token}`,
      },
    });
    
    console.log(data);

    const userData = {
      nickname: data.kakao_account.profile.nickname,
    };

    return userData;
  }
}

...생략

API 작성

작성한 기능을 토대로 app.js에서 API를 만들어줍니다.

클라이언트로부터 인가코드를 받아 token을 발급받고, 해당 token으로 유저 정보를 받아옵니다.

// src/app.js

...생략

app.post("/login", async (req, res, next) => {
  console.log("/login start");
  try {
    const { code } = req.body;

    const { access_token } = await KakaoClient.getToken(code); // 토큰 받아오기
    const userData = await KakaoClient.getUserData(access_token); // 유저 정보 받아오기

    // 그 후 DB로 사용자 등록 처리
    // 세션 or 토큰 처리
    // 등등 로그인 관련 처리를 해줘야 함

    res.status(200).json(userData);
  } catch (error) {
    console.error(error);

    const errorData = {
      message: "Internal server error.. :(",
    };
    res.status(500).json(errorData);
  }

  console.log("/login finish");
});

...생략

다 완성했습니다!
이제 프론트엔드에서 해당 API를 호출해보도록 할까요?😊

💡 실제 로그인이라면 DB를 조회하고, 해당 서비스의 세션 or 토큰을 발급해주는 등, 다른 작업들도 해야해요. 하지만 이 글에선 그런 내용은 다루지 않아요. :)

4. [프론트엔드] 로그인 API 호출하기

로그인 버튼을 누르면 Kakao 로그인 페이지와 동의 화면이 나오고, 미리 설정해둔 redirect_uri 주소로 필요한 인가코드를 전달받았어요.

이제 그 인가코드를 파싱해 아까 작성한 백엔드 API를 호출하겠습니다.

Callback component 만들기

redirect_uri로 설정했던 주소로 라우팅됐을 때 보여질 컴포넌트를 만들어봅시다.

이 컴포넌트에서 url을 읽어, query parameter에서 code를 가져올거예요.
그 후, code값을 body에 실어 API를 호출하겠습니다.

KakaoCallback.js 파일을 만들고 아래처럼 작성합니다.

// src/KakaoCallback.js

import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

function KakaoCallback() {
  const [code, setCode] = useState("");
  const navigate = useNavigate();

  /**
   * @description 로그인하기
   */
  const fetchLogin = useCallback(
    async (code) => {
      try {
        const param = {
          code,
        };

        const response = await (
          await fetch("http://localhost:3001/login", {
            method: "POST",
            headers: {
              "Content-Type": "application/json",
            },
            body: JSON.stringify(param), // string으로 전달해야함
          })
        ).json();

        console.log(response); // { nickname: '#######' }

        navigate("/main"); // API 호출 성공 시 메인 페이지로 이동
      } catch (error) {
        alert("Function fetchLogin error!");
        console.error(error);
      }
    },
    [navigate]
  );

  /**
   * @description login API fetch
   */
  useEffect(() => {
    if (code) {
      fetchLogin(code);
    }
  }, [code, fetchLogin]);

  /**
   * @description code 값 가져오기
   */
  useEffect(() => {
    const Address = new URL(window.location.href); // url 가져오기
    const code = Address.searchParams.get("code") || ""; // 👈 code value

    setCode(code);
  }, []);

  return <div className="App">Wait....</div>;
}

export default KakaoCallback;

Callback routing 하기

만든 컴포넌트를 라우팅합니다.

// src/index.js

...생략
import KakaoCallback from "./KakaoCallback";

...생략

<Routes>
  <Route path="/" element={<App />} />
  <Route path="main" element={<Main />} />
  <Route path="callback/kakao" element={<KakaoCallback />} /> {/* 👈 이 부분 추가 */}
</Routes>
  
...생략

이제 다 완성되었습니다!

테스트

잘 되는군요! (로그인하는 부분은 지웠고, 동의 받는 화면은 이미 진행을 해서 나오지 않았습니다.)

💡 전체 소스코드는 GitHub에 올려두었어요. :)


문제사항 발생 시

Kakao API 호출 시에 에러가 발생한다면, Kakao developer - 문제 해결에서 확인해보세요.
error 내용에 특정 code값이 있을거예요.🙄


의견은 언제든 댓글로 남겨주세요. 🙂

profile
블로그 옮겨요! https://dev-gingaminga.tistory.com

0개의 댓글