React | REST API๋ฅผ ์ด์ฉํ Kakao ์์ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ ๊ตฌํ
2์ฐจ ํ๋ก์ ํธ '๋ฎค์ฆ'์์๋ ๋น ๋ฅด๊ณ ๊ฐํธํ ์๋น์ค๋ฅผ ์ ๊ณตํ๋ ค๋ ๋ชฉ์ ๋ ์์๊ณ
๋ค๋ฅธ ์น์์ ๋ง์ด ์ฐ์ด๋ ์ธ๋ถ API๋ฅผ ์จ๋ณด๊ณ ์ถ๊ธฐ๋ ํด์ ์นด์นด์ค ์์
๋ก๊ทธ์ธ API๋ฅผ ์ฐ๋ํ๋ ๋ฐฉ์์ ์ ํํด์ ์งํํ๋ค.
์นด์นด์ค API ๋ก๊ทธ์ธ Flow
1. ์นด์นด์ค ๋ก๊ทธ์ธ ์ฐฝ์ ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด์ฌ์ค๋ค.
2. ํด๋ผ์ด์ธํธ๋ ๋ก๊ทธ์ธ
3. ๋์ํญ๋ชฉ ์ฒดํฌํ๊ณ ์นด์นด์ค API ์๋ฒ์ ๋ก๊ทธ์ธ ์ ๋ณด๋ฅผ ๋๊ธด๋ค.
4. ์นด์นด์ค API ์๋ฒ๋ ์ ๋ฌ ๋ฐ์ ์ ๋ณด๋ฅผ ํ์ธํ๊ณ ์๋น์ค ์๋ฒ์ Redirect URI๋ก ์ธ๊ฐ์ฝ๋๋ฅผ ๋ฐ๊ธํด์ค๋ค.
5. ์ธ๊ฐ์ฝ๋๋ฅผ ํตํด ์นด์นด์ค API ์๋ฒ๊ฐ ๋ก๊ทธ์ธ ์ ์งํ ์ ์๋ ํ ํฐ์ ๋ฐ๊ธํ๋ค.
์ฐ๋ฆฌ ํ์ ์ด๋๊น์ง ํ๋ก ํธ๊ฐ ํ๊ณ ๋ฐฑ์๋๊ฐ ์ด๋์๋ถํฐ ๋งก์ ํ ์ง ์ ํ๋ค.
๐ ์ฌ๊ธฐ์ ์ฃผ์ํ ์ ์ ํ ํฐ์ ์นด์นด์ค ์๋ฒ๋ก๋ถํฐ ๋ฐ์ง๋ง, ํ ํฐ์ ๊ทธ๋๋ก ์ฌ์ฉํ๋ฉด ํดํน ๋นํ๋ฉด
์ฌ์ฉ์์ ์นด์นด์ค ๊ณ์ ์ ์๋ ๋ชจ๋ ์ ๋ณด๊ฐ ๋ค ํธ๋ฆด ์๋ ์๊ธฐ ๋๋ฌธ์ ์นด์นด์ค ์๋ฒ์์ ๋ฐ๊ธ ๋ฐ์ ์ฌ์ดํธ ์ ์ฉ ํ ํฐ์ผ๋ก ๋ฐ๊ฟ์ฃผ์ด์ผ ํ๋ค.
Frontend ์ญํ
์นด์นด์ค๋ก๋ถํฐ ์ธ๊ฐ์ฝ๋๋ฅผ ๋ฐ๊ณ ๋ฐ์ ์ธ๊ฐ์ฝ๋๋ฅผ ๋ฐฑ์๋์ ๋๊ฒจ์ค๋ค.
์ธ๊ฐ์ฝ๋๋ฅผ ๋๊ธฐ๋ฉด ๋ฐฑ์๋๋ก๋ถํฐ ์ฌ์ดํธ์์ ์ธ ํ ํฐ์ ๋ฐ๊ธ ๋ฐ๋๋ค.
Backend ์ญํ
ํ๋ก ํธ์๋์๊ฑฐ ์ธ๊ฐ์ฝ๋ ๋๊ฒจ ๋ฐ๊ณ , ์นด์นด์ค๋ก๋ถํฐ ํ ํฐ์ ๋ฐ๊ธ ๋ฐ๋๋ค.
ํด๋น ํ ํฐ์ ๋ด๊ธด ์ ์ ์ ๋ณด๋ฅผ ํ์ฉํด ์ฌ์ดํธ ์ ์ฉ ํ ํฐ์ผ๋ก ์๋ก ๋ฐ๊ธ ๋ฐ์ ํ๋ก ํธ์๋์๊ฒ ๋๋ ค์ค๋ค.
๐ ์นด์นด์ค ๋ก๊ทธ์ธ API ์ฐ๋ ์์
Kakao Developer ์ค์ -> ๊ฐ๋ฐํ๊ฒฝ์ค์ -> ๋ก๊ทธ์ธ ๊ตฌํํ๊ธฐ
1. Kakao Developer ์ค์
์๋ ๊ฒฝ๋ก๋ก ๋ค์ด๊ฐ์ ์ค์
https://developers.kakao.com/console/app
step 1 ๋ด ์ ํ๋ฆฌ์ผ์ด์ ๋ฑ๋ก
์ ํ๋ฆฌ์ผ์ด์ ์ถ๊ฐํ๊ธฐ
step 2 ์นด์นด์ค ๋ก๊ทธ์ธ ํ์ฑํ
๋ด ์ ํ๋ฆฌ์ผ์ด์ -> ์ ํ ์ค์ / ์นด์นด์ค๋ก๊ทธ์ธ -> ์นด์นด์ค๋ก๊ทธ์ธ ํ์ฑํ
step 3 Web ํ๋ซํผ ๋ฑ๋ก
๋์ค์ ๋ฐฐํฌํ ์ฃผ์๋ฅผ ์ ์ผ๋ฉด ๋๋ค.
๋ฐฐํฌํ๊ธฐ ์ ์ด๊ธฐ ๋๋ฌธ์ localhost:3000 ์ผ๋ก ์ผ๋จ ์ค์
step 4 Redirect URI ๋ฑ๋ก
์นด์นด์ค ๋ก๊ทธ์ธ ๋ฉ๋ด์ ๋ค์ด๊ฐ์ ์ถ๊ฐ
๊ฒฝ๋ก ์ค์ ์ ๋ฐฑ์๋์ ํ์ํด์ ๋ง์ถ๋ฉด ๋๋๋ฐ, ๋ด๊ฐ ๋จผ์ ์ค์ ํ๊ณ ์จ๋ฌ๋ผ๊ณ ํ๋ค.
โ ๏ธํ๋ก ํธ์๋๊ฐ ์ ๊ทผํ ์ ์๋ Host ๋ก ์ง์ ํ๊ธฐ!
step 5 ๋์ํญ๋ชฉ ์ค์
์นด์นด์ค ๋ก๊ทธ์ธ ํ ์ค์ ํ ๋์ํญ๋ชฉ์ ๋ํด ์๋ต ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๋ค.
๋ด ์ ํ๋ฆฌ์ผ์ด์
-> ์ ํ์ค์ -> ๋์ํญ๋ชฉ
์์ ๊ฐ์ด ์ค์ ํด์ฃผ๋ฉด ์นด์นด์ค ๋ก๊ทธ์ธ API ์ฌ์ฉ์ ํ์ํ ์ค์ ์ด ์๋ฃ๋๋ค.
์ด์ ์ธ๊ฐ์ฝ๋๋ฅผ ์นด์นด์ค ์๋ฒ์ ์์ฒญํ ๋ ํ์ํ ํค๋ค์ ํ์ธํด์ฃผ๋ฉด ๋๋ค.
2. ๊ฐ๋ฐํ๊ฒฝ ์ค์
step 1 ์นด์นด์ค ๋ก๊ทธ์ธ ๋ฒํผ ํด๋ฆญํ์ ๋ KAKAO_AUTH_URL๋ก ์ด๋
//KakaoAuthUrl.js
export const KAKAO_AUTH_URL = `https://kauth.kakao.com/oauth/authorize?
client_id=${REACT_APP_CLIENT_ID}
&redirect_uri=${REACT_APP_REDIRECT_URI}
&response_type=code`;
๋๋ ๋ฐ๋ก KakaoAuthUrl.jsํ์ผ์ ๋ง๋ค์ด Kakao Auth Url์ ๋ด๋ณด๋ด์ฃผ์๋ค.
client_id ๊ฐ์ผ๋ก Rest API Key ๋ฅผ ๋ฃ๊ณ redirect_uri ๊ฐ์ผ๋ก Redirect URI๋ฅผ ๋ฃ์ด์ฃผ๋ฉด ๋๋ค.
//SignIn.js
...
function handleLogin() {
window.location.href = KAKAO_AUTH_URL;
}
...
return (
...
<KakaoButton imgUrl={logoBtnImg} onClick={handleLogin} />
)
์นด์นด์ค๋ก ๋ก๊ทธ์ธํ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ์ด Url์ฃผ์๋ก ์นด์นด์คํก ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋๋๊ฒ ๊ตฌํํด์ค์ผ ํ๋ค.
window.location.href๋ฅผ ์ด์ฉํด์ ๋ค๋ฅธ URL๋ก ์ด๋์ํฌ ์ ์๋ค.
๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋ํ๋ฉด ์นด์นด์ค ๋ก๊ทธ์ธ ํ๋ฉด์ด ๋ํ๋๊ณ ์ฌ๊ธฐ์ ์นด์นด์ค ๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธํ๊ณ ๋์ํญ๋ชฉ ์ฒดํฌํ๊ณ ๋๋ฉด
์๋์ ๊ฐ์ด ๋น ํ๋ฉด์ด ๋์จ๋ค.
๋๋ ๊ณ์ ์ ๋นํ๋ฉด์์ ์๋ฌ๊ฐ ์์๋ค.
๋ฐฑ์๋ ํต์ ์ ๋ฌธ์ ๊ฐ ์๋ ๊ฑด์ง ๋ฐฑ์๋ ํ์๋ถ์ด๋ ๊ณ์ ์ด์ ๋ฅผ ์ฐพ์๋ดค์๋๋ฐ
์ธ๊ฐ์ฝ๋๋ ๋ฐ๋ ๊ฒ์ ๋ฌธ์ ๊ฐ ์์ด๋ณด์๊ณ ๋ฃจํธ๋ฅผ ๊ณ์ ๋ชป ์ฐพ๋๋ค๊ณ ๋์์๋ค.
์ง๊ธ ์๊ฐํ๋ฉด ํ๋ฌดํ๋ ๊ฒ ๋ฐฑ์๋๋ ํต์ ํ์ง๋ ์์๋๋ฐ ์ํ ๋ฐ์ ๋ฌธ์ ๋ฅผ ์ฐพ์๋ดค๋ ๊ฒ..
๋ผ์ฐํฐ ํ์ด์ง๋ฅผ ๋ณด๋ ์ ํ์ด์ง๊ฐ ์๋๋ฐ ์ด๋ ์ฃผ์๋ก ๋ณด์ฌ์ค์ง ์ ํด๋์ง ์์๋ ๊ฑฐ์๋ค.
Redirect URI๋ก ์ค์ ํด๋๋ ํด๊ฒฐ๋๋ค.
step 2 ์นด์นด์ค API์ ์ธ๊ฐ ์ฝ๋ ๋ถ์ฌ ๋ฆฌ๋ค์ด๋ ํธ
์์ ์๋ ๋น ํ๋ฉด ์ฃผ์์ฐฝ์ redirect_uri ์ฃผ์ ๋ค์ ?code= ์ธ๊ฐ์ฝ๋ ๊ฐ ๋์ค๋๋ฐ ์ด ์ธ๊ฐ์ฝ๋๋ฅผ ๊ฐ์ ธ์์
๋ฐฑ์๋์ ๋๊ฒจ์ฃผ์ด ํ ํฐ์ ๋ฐ์์ผ ํ๋ค.
๋๋ ์ด ์ฃผ์์์ ์ธ๊ฐ์ฝ๋๋ฅผ ๋นผ๊ธฐ ์ํด useLocation์ ์ผ๋ค.
location ์ ์๋ 'search' key์ '?code= ์ธ๊ฐ์ฝ๋'๊ฐ ๋ด๊ฒจ ์์ด splitํ์ฌ '=' ์ผ๋ก ๋ถ๋ฆฌํ๊ณ ๋๋ฒ์งธ ํญ๋ชฉ์ ๊ฐ์ ธ์๋ค.
const location = useLocation();
const CODE = location.search.split('=')[1];
์นด์นด์ค API์ ์ธ๊ฐ์ฝ๋๋ฅผ client_id, redirect_uri ๊ฐ๊ณผ ํจ๊ป body์ ๋ด์ ์์ฒญํ๋ค.
//KakaoAuth.js
...
fetch("https://kauth.kakao.com/oauth/token", {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
body: `grant_type=authorization_code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&code=${CODE}`,
})
step 3 ์ธ๊ฐ์ฝ๋๋ฅผ ํตํด ์นด์นด์คAPI๋ก๋ถํฐ ๋ฐ๊ธ๋ฐ์ ํ ํฐ์ ๋ฐฑ์๋์ ์ ์ก
//KakaoAuth.js
fetch( ... )
.then(res => res.json())
.then(data => {
if (data.access_token) {
fetch("๋ฐฑ์๋ API์ฃผ์", {
method: 'POST',
headers: { 'Content-Type': 'application/json;charset=utf-8' },
body: JSON.stringify({
kakaoAccessToken: data.access_token,//์นด์นด์ค๋ก๋ถํฐ ๋ฐ๊ธ๋ฐ์ ํ ํฐ์ ๋ฐฑ์๋๊ฐ ์ค ํค๊ฐ์ ๋ง์ถค
nickname: '',//์ฌ์ฉ์์ ์ด๋ฆ์ ์ธ ํ์ด์ง๊ฐ ์์ด์ ๊ฐ์ด ์์ฒญ
}),
})
step 4 ๋ฐฑ์๋์๊ฒ์ ํ ํ๋ก์ ํธ ์ฌ์ดํธ์์ ์ฌ์ฉํ ์ ์ฉ ํ ํฐ์ ๋ค์ ๋ฐ๊ธฐ
๋ฐฑ์๋์ ์นด์นด์ค ์๋ฒ๋ก๋ถํฐ ์ ๋ฌ ๋ฐ์ ํ ํฐ์ ๋ณด๋ด์คฌ๋ค๋ฉด,
๋ฐฑ์๋์์ ๊ทธ ํ ํฐ์ ๋ณด์์ ์ํด ์ฌ์ดํธ ์ ์ฉ ํ ํฐ์ผ๋ก ๋ฐ๊พธ์ด ๋ค์ ๋ณด๋ด์ค๋ค.
ํ ํฐ์ ๋ฐ์ผ๋ฉด local storage ์ ์ ์ฅํ๋ค.
//KakaoAuth.js
.then(response => response.json())
.then(result => {
if (result.token) {
localStorage.setItem('TOKEN', result.token);
localStorage.setItem('username', result.nickname);
}
});
KakaoAuth.js ํ์ผ ์ ์ฒด ์ฝ๋
์ธ๊ฐ์ฝ๋๋ฅผ ํตํด ํ ํฐ ์์ฒญํ๊ณ ๋ฐ๊ธ๋ฐ์ ๋ก์ปฌ์คํ ๋ฆฌ์ง์ ์ ์ฅ๋์ด ๋ก๊ทธ์ธ์ด ์คํ๋๋ ๊ณณ์ด๋ค.
๋๋ ์ฌ์ฉ์์ ๋ก๊ทธ์ธ์ด ์ง์ฐ๋๋ ๊ฑธ ๋๋นํด์ ์คํผ๋(๋ก๋ฉ๋ฐ)๋ฅผ ์ถ๊ฐํด์คฌ๋ค.
import React, { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import styled from 'styled-components';
import { PulseLoader } from 'react-spinners';
import { API } from '../../../config';
function KakaoAuth() {
const navigate = useNavigate();
const location = useLocation();
const CODE = location.search.split('=')[1];
function getKakaoToken() {
fetch(`${API.kakaoAuthToken}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8',
},
body: `grant_type=authorization_code&client_id=${CLIENT_ID}&redirect_uri=${REDIRECT_URI}&code=${CODE}`,
})
.then(res => res.json())
.then(data => {
if (data.access_token) {
fetch(`${API.kakaoLogin}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json;charset=utf-8' },
body: JSON.stringify({
kakaoAccessToken: data.access_token,
nickname: '',
}),
})
.then(response => response.json())
.then(result => {
if (result.token) {
localStorage.setItem('TOKEN', result.token);
localStorage.setItem('username', result.nickname);
alert('๋ก๊ทธ์ธ ์ฑ๊ณตํ์ด์!');
navigate('/');
}
});
} else {
alert('๋ค์ ํ ๋ฒ ๋ก๊ทธ์ธํด์ฃผ์ธ์!');
navigate('/signin');
}
});
}
useEffect(() => {
getKakaoToken();
}, []);
return (
<Spinner>
<PulseLoader color="#6200EE" margin={8} size={20} />
</Spinner>
);
}
export default KakaoAuth;
const Spinner = styled.div`
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
`;
๐ .envํ์ผ๋ก ์์ ํ๊ฒ ํ์ผ ๊ณต์
๊ทธ๋ฆฌ๊ณ client_id, redirect_uri๋ ๋ณด์์ ์ด์ ๋ก .env ํ์ผ์ ์์ฑํด์ ์ฝ๋๋ฅผ ๊ณต์ ํ๋ ๊ฒ ์ข๋ค.
๊นํ์ ๊ทธ๋ฅ ์ฌ๋ฆฌ๋ฉด client_id, redirect_uri๋ฅผ ๋ค ๊ณต์ ํ๊ฒ ๋๊ธฐ ๋๋ฌธ์ ์ํํ ์๋ ์๋ค.
React ๋ ์์ REACT_APP_๋ณ์๋ช
์ผ๋ก ๋ฐ๊ฟ์ค์ผ ํ๋ค.
//.env.js
REACT_APP_CLIENT_ID = ...
REACT_APP_REDIRECT_URI = ...
๋ฐ๋ผ์ client_id, redirect_uri ๊ฐ ์ฐ์ธ ๊ณณ์๋ ์๋์ ๊ฐ์ด process.env.REACT_APP_๋ณ์์ด๋ฆ ์ผ๋ก ๋ฐ๊พผ๋ค.
//KakaoAuth.js
body: `grant_type=authorization_code&client_id=${process.env.REACT_APP_CLIENT_ID}&redirect_uri=${process.env.REACT_APP_REDIRECT_URI}&code=${CODE}`,