최근에 참여한 해커톤에서 백엔드 팀원과 함께 소셜 로그인을 구현하려했으나 결국엔 구현하지 못했다. 너무 프론트 입장에서만 생각하여 막상 협업하려니 서버와 통신할 때 어떤 정보를 요청해야되는지, 내가 지금 무엇을 하고 있는지 헷갈렸던 게 패착인 듯 하다.
그래서 KAKAO developers의 공식문서와 이것저것 찾아보며 백엔드와의 협업을 기저에 두며 카카오 로그인을 구현해보려고 한다.
미니 게시판 (메인 페이지 / 로그인페이지)
먼저 메인 페이지와 로그인 페이지를 Route를 통하여 구분지어주었으며, 페이지의 컴포넌트들을 styled-components 방식으로 간단히 스타일링했다.
아래는 메인페이지, 상세페이지, 로그인페이지 스타일링 완료 후 모습이다.

상세페이지 (글 목록 눌렀을경우)

상세페이지에 들어가면 해당 글의 내용을 볼 수 있다.
( 페이지 간 state를 useNavigate - useLocation 을 통해 주고받는 형식으로 구현했다.)
로그인페이지

로그인페이지의 경우 카카오에서 제공하는 디자인 가이드를 사용했다. https://developers.kakao.com/tool/resource/login

먼저 위 그림의 Step 1을 구현해보자.
Login.js 에서 이미지를 클릭했을 때 카카오 로그인 요청을 하여 사용자로부터 인증 및 동의 요청을 받아 로그인하고Auth.js) 를 통하여 이동하여 받아온 인가 코드로 토큰 발급 요청을 하여 토큰 발급을 하고,일단 로그인 요청을 카카오에 한다는 말은 미리 발급받은 API KEY를 통하여 kakao 로그인 링크(인가코드요청주소)로 이동한다고 보면된다.
// kakao로그인 링크
const 인가코드요청주소 = `https://kauth.kakao.com/oauth/authorize?clident_id=${APP_KEY}&redirect_url=${REDIRECT_URL}&response_type=code`;
Login.js 에서 카카오 로그인하는 이미지에 a태그를 통해 위에 있는 카카오 인가코드요청주소로 이동하게 해주었다.
인가코드요청주소의 경우 https://kauth.kakao.com/oauth/authorize 뒤에 쿼리파라미터 형식으로 client_id (REST API 키) 와 REDIRECT_URL을 명시해주고, response_type=code 을 명시해주면 된다.
APP_KEY와 REDIRECT_URL 의 경우 프로젝트 루트 경로에 있는 .env파일에서 저장해놓고 (꼭 REACT_APP~ 형식으로 저장해야 한다! ) App.js에서 APP_KEY 와 REDIRECT_URL이라는 변수로 export하여 사용했다.
성공적으로 로그인 요청을 하면

이 페이지로 넘어가고, 확인하고 계속할 경우 로그인 페이지로 이동한다!

카카오가 발급한 인가코드가 쿼리 스트링 형식으로 code=블라블라 형식으로 원래 설정한 REDIRECT_URI 뒤에 붙여진 상태로 전달되어진다!
이렇게 받은 인가 코드를 통해 토큰 발급을 요청해보자.
window.location.href 로 현재 주소(REDIRECT_URL + 인가 코드)를 반환할 수 있으니,
const token = new URL(window.location.href).searchParams.get("code");
.searchParams.get("code") 를 통해 쿼리스트링 형식의 해당 key에 대한 value 값을 받아올 수 있다. 이를 통해 인가 코드를 token 에 할당했다.
const getToken = async () => {
const token = new URL(window.location.href).searchParams.get("code");
const res = axios.post(
"https://kauth.kakao.com/oauth/token",
{
grant_type: "authorization_code",
client_id: APP_KEY,
redirect_uri: REDIRECT_URI,
code: token,
},
{
headers: {
"Content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
}
);
return res;
};
추출한 인가 코드를 활용하여 "https://kauth.kakao.com/oauth/token" 에 POST 요청을 하여 토큰을 발급한다! ( POST 요청 보내는 양식은 https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api 에 지정되어있으니 꼭 참고하자 )
Auth.js
import React, { useEffect } from "react";
import axios from "axios";
import { APP_KEY, REDIRECT_URI } from "../App";
import { useNavigate } from "react-router-dom";
function Auth() {
const navigate = useNavigate();
const getToken = async () => {
const token = new URL(window.location.href).searchParams.get("code");
const res = axios.post(
"https://kauth.kakao.com/oauth/token",
{
grant_type: "authorization_code",
client_id: APP_KEY,
redirect_uri: REDIRECT_URI,
code: token,
},
{
headers: {
"Content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
}
);
return res;
};
useEffect(() => {
getToken()
.then((res) => {
if (res) {
localStorage.setItem("token", JSON.stringify(res.data.access_token));
navigate("/");
}
})
.catch((err) => console.log(err));
}, []);
return <></>;
}
export default Auth;
useEffect 훅을 통하여 Auth 로 리다이렉트 되었을 때 쿼리 스트링 형식으로 전달받은 인가 코드를 통하여 토큰을 발급받고 localStroage에 저장한다.
Home.js에서 발급받은 토큰으로 유저의 nickname 과 profile_image 를 가져와서 화면에 띄우자. 현재 로컬 스토리지에 저장한 토큰의 경우 유효기간이 있기 때문에, 요청에 실패했을 경우 원래의 token을 삭제함으로써 로그인을 다시 하게끔 하면 되겠다.
Home.js
const getUserData = async (token) => {
const user = await axios.get(`https://kapi.kakao.com/v2/user/me`, {
headers: {
Authorization: `Bearer ${token}`,
"Content-type": "application/x-www-form-urlencoded;charset=utf-8",
},
});
console.log(user);
return user.data;
};
useEffect(() => {
const token = localStorage.getItem("token");
if (token) {
const token = localStorage.getItem("token");
getUserData(token)
.then((data) => {
setUserInfo(data.properties);
})
.catch((err) => {
console.log(err);
localStorage.removeItem("token");
});
}
}, []);
useState 훅을 통하여 userInfo 에 가져온 유저 정보를 저장하고, 비구조화 할당을 통해 이미지와 사용자 닉네임을 가져와 컴포넌트에 뿌렸다!
const [userInfo, setUserInfo] = useState({});
const { nickname, profile_image } = userInfo;
return (
<HomeContainer>
<UserInfo>
<UserImage>
<img src={profile_image} alt="유저 이미지"/>
</UserImage>
<UserName>{nickname}님 안녕하세요!</UserName>
잘 받아와지는 걸 볼 수 있다 ㅎㅎ

마지막으로 Home.js 게시물을 클릭했을 때 localStroage에 저장된 토큰의 저장 여부에 따라, 토큰이 있을 경우 /post/:postID로 이동하고 없을 경우 로그인 페이지로 이동하도록 구현하면 끝이다!
<PostLists>
{postItem.map((item, idx) => (
<PostItem
className="postItem"
key={item.id}
onClick={() => {
if (localStorage.getItem("token")) {
navigate(`/post/${item.id}`, { state: postItem[idx] });
} else {
navigate("/login");
}
}}
>
<div className="post-title">{item.title}</div>
<div className="post-time">{item.time}</div>
</PostItem>
))}
</PostLists>
완성이다!
프론트엔드에서 미리 발급받은 API_KEY로 로그인 요청을 카카오 서버에 하여 받아온 인가코드를 백엔드 서버로 POST하면, 백엔드 서버에서 인가코드로 토큰을 발급받아서 로그인 기능을 구현하여 자체 jwt 액세스토큰을 발급해서 프론트엔드에 보내주면, 프론트엔드에서는 토큰을 로컬스토리지에 저장하는 식으로 로그인 기능을 구현할 수 있을 것 같다..!