Kakao REST API
- 시작하기에 앞서 해당 포스팅에 나오는 코드의 서버에서 카카오로 API 요청을 할때는
require('request-promise')
모듈을 사용합니다.
- 해당 게시글은 서비스에서 로그인 기능을 카카오 REST API를 사용해 구현하는 내용입니다.
- 또한 모든 클라이언트의 요청은 클라이언트에서 바로 실행되는게 아닌 서버를 거쳐 서버의 미들웨어에서 카카오 서버로 수행됩니다.
- 에제와 관련된 카카오 REST API 구현을 위해 필요한 코드는 함수로 정리를 해놨습니다.
const path = require('path');
const rp = require('request-promise');
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') });
exports.getToken = async (AUTHORIZE_CODE) => {
const options = {
uri: 'https://kauth.kakao.com/oauth/token?response_type=code&client_id='+process.env.REST_API_KEY+'&redirect_uri='+process.env.REDIRECT_URI,
method: 'POST',
form: {
"grant_type": "authorization_code",
"client_id": process.env.REST_API_KEY,
"redirect_uri": process.env.REDIRECT_URI,
"code": AUTHORIZE_CODE,
"client_secret": process.env.CLIENT_SECRET
},
json: true
}
return await rp(options);
}
exports.getUserInfo = async (access_token) => {
const options = {
uri: 'https://kapi.kakao.com/v2/user/me',
method: 'GET',
headers: {
"Content-Type": 'application/x-www-form-urlencoded;charset=utf-8',
"Authorization": "Bearer "+access_token
},
json: true
};
try {
const body = await rp(options);
const nickname = body.properties.nickname;
const profile_image = body.properties.profile_image;
return {nickname, profile_image};
} catch(err) {
throw Error(err);
}
}
exports.logout = async (access_token) => {
const options = {
uri: 'https://kapi.kakao.com/v1/user/logout',
method: 'POST',
headers: {
"Authorization": "Bearer "+access_token
},
json: true
}
try {
return await rp(options);
} catch(err) {
throw Error(err);
}
}
exports.unlink = async (access_token) => {
const options = {
uri: 'https://kapi.kakao.com/v1/user/unlink',
method: 'POST',
headers: {
"Authorization": "Bearer "+access_token
},
json: true
}
try {
return await rp(options);
} catch(err) {
throw Error(err);
}
}
exports.logoutWithKakaoAccount = async () => {
const options = {
uri: 'https://kauth.kakao.com/oauth/logout?client_id='+process.env.REST_API_KEY+'&logout_redirect_uri='+process.env.LOGOUT_REDIRECT,
method: 'GET',
json: true
}
try {
return await rp(options);
} catch(err) {
throw Error(err);
}
}
exports.renewExpiredToken = async (refresh_token) => {
const options = {
uri: 'https://kauth.kakao.com/oauth/token',
method: 'POST',
form: {
"client_id": process.env.REST_API_KEY,
"refresh_token": refresh_token,
"client_secret": process.env.CLIENT_SECRET
},
json: true
}
try {
return await rp(options);
} catch(err) {
throw Error(err);
}
}
로그인

그림[1]
- 그림[1]은 로그인 과정을 나타내는 사진이며 이 과정을 설명하겠습니다. (회원가입은 미구현).
카카오 로그인 요청

<a href="/auth/kakao"><img src="/public/images/kakao.png" />
- 맨 처음 말했다시피, 클라이언트의 모든 요청은 카카오 서버로 바로 보내는 것이 아닌 서비스의 서버를 거쳐 카카오 서버로 요청을 보낸다.
- 위처럼 카카오에서 제공하는
Login with Kakao
버튼을 만들어 서비스 서버의 /auth/kakao
경로로 get
요청을 했다.
인가코드 받기 요청
exports.authenticate = async (req, res, next) => {
try {
const kakaoAuthURL = 'https://kauth.kakao.com/oauth/authorize?response_type=code&client_id='+process.env.REST_API_KEY+'&redirect_uri='+process.env.REDIRECT_URI+'&prompt=login';
return res.status(302).redirect(kakaoAuthURL);
} catch(err) {
return next(err);
}
}
- 클라이언트로부터 해당 요청을 받은 서버는 해당 경로의 미들웨어를 실행시키며 이는 위와같다.
- 해당 미들웨어는 카카오에서 제공하는 경로와 공식 문서에서 요구하는
REST_API_KEY
, REDIRECT_URI
를 추가해 클라이언트에게 반환한다.
인증 및 동의 요청

- 서버에서 반환받은 화면을 클라이언트는 위 사진처럼 볼 수 있으며 이는 카카오에서 제공하는 인증 및 동의 요청 화면이다.
로그인 및 동의


- 위처럼 동의 화면이 나타난다.
- 클라이언트가 동의까지 누른 후 다음 버튼을 누르면 이 정보들은 카카오 서버로 들어간다.
인가 코드 발급

- 신원을 확인한 카카오 서버는 인가코드를 클라이언트에게 발급해주며 클라이언트는 다시 https://developers.kakao.com 의
내 애플리케이션
> 제품 설정
> Kakao Login
에서 설정한 RedirectURI
를 통해 해당 미들웨어로 인가 코드와 함께 요청을 보낸다.
- 이경우
http://localhost/auth/kakao/callback
- 사실 위의 설명이 본인이 알기로 맞는 과정인데 카카오 서버에서 인증을 마친 후 인가코드를 바로
RedirectURI
경로의 미들웨어로 보내는지 클라이언트에 보내 거기서 RedirectURI
경로로 요청을 보내는지 확인을 못했다.
인가 코드로 토큰 발급 요청
const kakao = require('../kakao/kakaoService');
exports.authorize = async (req, res, next) => {
const AUTHORIZE_CODE = req.query['code'];
try {
const {
access_token,
id_token,
refresh_token
} = await kakao.getToken(AUTHORIZE_CODE);
req.session.access_token = access_token;
req.session.id_token = id_token;
req.session.refresh_token = refresh_token;
return res.status(200).redirect('/user');
} catch(err) {
next(err);
}
}
- 위 미들웨어가 카카오 데브의 앱에 등록된 RedirectURI인
/auth/kakao/callback
경로에 해당하는 함수이다.
- 인증을 마친 클라이언트는 카카오 서버로부터 인가코드를 받고 서비스 서버에 인가코드를 보내며 위 함수처럼 받을 수 있다.
- 인가코드를 전달받은 서비스 서버는 다시 카카오에게 코드를 포함한 요청을 보내 토큰을 발급받을 수 있다.
- 위 코드에서 토큰 발급에 관한 함수인
kakao.getToken(AUTHORIZE_CODE)
는 아래 토큰발급에서 설명하겠습니다.
- 토큰을 받은 서버는
req
객체의 세션에 토큰들을 저장해 stateful
한 상태를 유지할 수 있으며, 마지막으로 본인의 경우/user
경로의 미들웨어를 실행시켜준다.
토큰 발급
const rp = require('request-promise');
exports.getToken = async (AUTHORIZE_CODE) => {
const options = {
uri: 'https://kauth.kakao.com/oauth/token?response_type=code&client_id='+process.env.REST_API_KEY+'&redirect_uri='+process.env.REDIRECT_URI,
method: 'POST',
form: {
"grant_type": "authorization_code",
"client_id": process.env.REST_API_KEY,
"redirect_uri": process.env.REDIRECT_URI,
"code": AUTHORIZE_CODE,
"client_secret": process.env.CLIENT_SECRET
},
json: true
}
return await rp(options);
}
- 위의 인가 코드로 토큰 발급을 요청할때 쓰였던 함수인 getToken() 함수이며
request-promise
모듈을 사용해 카카오 서버로 요청을 보냈다.
카카오 로그인 완료, 토큰 정보 조회 및 검증
exports.showMain = async (req, res) => {
if(req.session.access_token) {
const {nickname, profile_image} = await kakao.getUserInfo(req.session.access_token);
const user = {
id: nickname,
address: profile_image
};
return res.render(path.join(__dirname, '../../views/user/user'), {user:user});
}
return res.render(path.join(__dirname, '../../views/user/loginPage'));
}
- 인가코드로 토큰 발급 요청 에서 발급받은 토큰을 세션에 저장한 뒤
/user
경로의 미들웨어에서 req.session
객체에 접근해 토큰을 가져와 이곳에서 유저 정보를 카카오 서버에게 요청한다.
const rp = require('request-promise');
exports.getUserInfo = async (access_token) => {
const options = {
uri: 'https://kapi.kakao.com/v2/user/me',
method: 'GET',
headers: {
"Content-Type": 'application/x-www-form-urlencoded;charset=utf-8',
"Authorization": "Bearer "+access_token
},
json: true
};
try {
const body = await rp(options);
const nickname = body.properties.nickname;
const profile_image = body.properties.profile_image;
return {nickname, profile_image};
} catch(err) {
throw Error(err);
}
}
- getUserInfo(access_token) 함수는 이런식으로 만들었다.

- 로그인을 마치면 위처럼 실제 카카오톡 어플에서 설정해 놓은 이름과 프로필 사진의 주소를 (로그인 할때 동의한) 클라이언트에 보낼 수 있다.
- 프로필 주소는 프론트의 글씨체가 너무 커 잘린 상태로 나와있는거같다.
로그아웃
- 사용자 액세스 토큰과 리프레시 토큰을 모두 만료시킨다.
- 로그아웃을 해도 카카오 계정과 서비스간의 세션은 만료되지 않으며, 로그아웃을 호출한 앱의 토큰만 만료된다.
- 카카오 계정과 서비스와의 연결을 아예 끊고싶으면 추가로 연결끊기도 해아한다.
<form id="signOut">
<button type="button" id="logOut" onclick="signOut()">Log out</button><br><br>
</form>
function signOut() {
$.ajax({
type: "delete",
url: '/user/logout',
data: {},
dataType:'text',
success: function(res) {
location.reload();
}
});
}
Log out
버튼을 통해 로그아웃 요청을 수행할 수 있다.
exports.signOut = async (req, res, next) => {
const body = kakao.logout(req.session.access_token);
req.session.destroy();
return res.status(200).end();
}
- 위 코드가 로그아웃 버튼을 클릭했을 시 실행되는 미들웨어 이며 카카오 서버로 로그아웃 요청을 보낸 후 세션 DB의 토큰들을 삭제한다.
- 카카오 서버로 로그아웃 요청을 수행하면 카카오는 해당 access_token과 refresh_token을 만료시킨다.
- 그 후 서비스 서버의 세션DB에 남아있는 만료되어있는 토큰들을
req.session.destroy()
를 통해 지워준다.
const rp = require('request-promise');
exports.logout = async (access_token) => {
const options = {
uri: 'https://kapi.kakao.com/v1/user/logout',
method: 'POST',
headers: {
"Authorization": "Bearer "+access_token
},
json: true
}
try {
return await rp(options);
} catch(err) {
throw Error(err);
}
}
- 위는 서버에서 카카오로 로그아웃 요청을 보낼 때 사용한 함수이다.
연결끊기

exports.disconnectKakao = async (req, res, next) => {
try {
const body = await kakao.unlink(req.session.access_token);
req.session.destroy();
return res.status(302).redirect('/user');
} catch(err) {
throw Error(err);
}
}
const rp = require('request-promise');
exports.unlink = async (access_token) => {
const options = {
uri: 'https://kapi.kakao.com/v1/user/unlink',
method: 'POST',
headers: {
"Authorization": "Bearer "+access_token
},
json: true
}
try {
return await rp(options);
} catch(err) {
throw Error(err);
}
}
- 로그아웃과 마찬가지로 카카오에 요청을 보낸 후 서버의 세션DB에 남아있는 토큰을 삭제했다.
References
Github