카카오 로그인 구현
간편하게 로그인 할 수 있게 카카오 API를 이용해서 로그인/로그아웃을 구현했다.
앱 키 & URI 설정
카카오 개발자에서 애플리케이션에 진행중인 프로젝트를 등록한다.
그럼 앱 키가 발행된다.
카카오 로그인 메뉴에 들어가서 활성화 설정과 OpenID Connect 활성화 설정을 ON으로 켜놓고, Redirect URI와 Logout Redirect URI도 설정한다.
Logout Redirect URI는 고급 탭에 있다.
이렇게 기초 설정을 다 하고 서버와 클라이언트 환경변수에 저장해둔다.
// .env
KAKAO_REST_API_KEY=
KAKAO_ADMIN_KEY=
KAKAO_LOGIN_REDIRECT_URI=
KAKAO_LOGOUT_REDIRECT_URI=
동의항목 설정
애플리케이션 - 동의항목 탭에 들어가서 필요한 정보에 대한 사용여부를 설정해준다.
나는 유저의 닉네임과 프로필 사진만 필요해서 아래와 같이 두가지만 설정했다.
로그인 버튼 다운
카카오 로그인 디자인 가이드를 준수한 버튼을 다운 받는다.
REST API 로그인
카카오 로그인 과정은 아래와 같다.
클라이언트에서 서버로 카카오 로그인 요청하고 서버에서 카카오 인증서버로 API요청을 해서 직접 클라이언트로 결과를 보내주는 방식이다.
인가 코드 받기
클라이언트에서 로그인 버튼을 클릭했을 때 인가코드를 받는 URL로 이동하도록 해준다.
/* 카카오 로그인 */
const onKaKaoLogin = useCallback((e) => {
e.preventDefault();
window.location.href = `https://kauth.kakao.com/oauth/authorize?client_id=${process.env.KAKAO_REST_API_KEY}&redirect_uri=${process.env.KAKAO_LOGIN_REDIRECT_URI}&response_type=code`;
}, []);
// ...code
<KakaoLogin onClick={onKaKaoLogin} />
성공적으로 이동할 경우 아래와 같이 인가코드 발급 과정을 거치게 된다. 이 때 유저가 로그인과 동의 항목에 대한 확인을 한다.
유저가 동의하고 계속 할 경우 서버에서 카카오 인증서버로 API 통신을 요청해야한다. 그 전에 토큰부터 발급받는다.
토큰 받기
/**
* @path {POST} /api/auth/kakao/login
* @description 카카오 로그인
*/
router.get('/auth/kakao/login', async (req, res, next) => {
try {
const code = req.query.code;
// 카카오 토큰 발급
const authToken = await axios.post(
'https://kauth.kakao.com/oauth/token',
{},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
grant_type: 'authorization_code',
client_id: process.env.KAKAO_REST_API_KEY,
code,
redirect_uri: process.env.KAKAO_LOGIN_REDIRECT_URI,
},
},
);
} catch (error) {
next(error);
}
});
토큰 발급 요청에 성공하면 아래와 같은 response 값을 받게되고, 나는 authToken에 저장했다.
발급 받은 토큰으로 로그인 한 유저의 정보를 가져온다.
// access_token으로 사용자 정보 가져오기
const authInfo = await axios.post(
'https://kapi.kakao.com/v2/user/me',
{},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer ' + authToken.data.access_token,
},
},
);
사용자 정보 가져오기
유저 정보를 가져오는데에 성공하면 수많은 Response 값들을 받게되고, 나는 authInfo에 저장했다.
그 중에 내가 필요한 데이터를 DB에서 사용하면 된다.
로그인 API 작성
DB에서 카카오로그인으로 가입/로그인 한 유저를 먼저찾는다.
// 카카오 로그인 가입 확인
const user = await User.findOne({
kakao_id: authInfo.data.id,
});
가입 했던 유저라면 바로 토큰을 발행하고 쿠키에 저장하고 홈페이지로 redirect시켜 로그인을 완료한다.
if (user) {
// 로그인
user.generateToken((error, user) => {
if (error) return res.status(400).send(error);
return res
.cookie('x_auth', user.token, { httpOnly: true })
.status(200)
.redirect(process.env.CLIENT_SERVER);
});
}
가입이 필요한 유저라면 카카오API를 사용해서 가져온 유저의 정보를 사용해서 DB에 가입시키고, 바로 로그인 과정을 동일하게 거친다.
else {
// 회원가입 후 로그인
await User.create({
kakao_id: authInfo.data.id,
kakao_access_token: authToken.data.access_token,
userAvatar: authInfo.data.properties.thumbnail_image,
userTitle: '한 줄 소개가 없습니다.',
userName: authInfo.data.properties.nickname,
}).then((user) => {
user.generateToken((error, user) => {
if (error) return res.status(400).send(error);
return res
.cookie('x_auth', user.token, { httpOnly: true })
.status(200)
.redirect(process.env.CLIENT_SERVER);
});
});
}
로그인 전체코드
/**
* @path {POST} /api/auth/kakao/login
* @description 카카오 로그인
*/
router.get('/auth/kakao/login', async (req, res, next) => {
try {
const code = req.query.code;
// 카카오 토큰 발급
const authToken = await axios.post(
'https://kauth.kakao.com/oauth/token',
{},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
params: {
grant_type: 'authorization_code',
client_id: process.env.KAKAO_REST_API_KEY,
code,
redirect_uri: process.env.KAKAO_LOGIN_REDIRECT_URI,
},
},
);
// access_token으로 사용자 정보 가져오기
const authInfo = await axios.post(
'https://kapi.kakao.com/v2/user/me',
{},
{
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Authorization: 'Bearer ' + authToken.data.access_token,
},
},
);
// 카카오 로그인 가입 확인
const user = await User.findOne({
kakao_id: authInfo.data.id,
});
if (user) {
// 로그인
user.generateToken((error, user) => {
if (error) return res.status(400).send(error);
return res
.cookie('x_auth', user.token, { httpOnly: true })
.status(200)
.redirect(process.env.CLIENT_SERVER);
});
} else {
// 회원가입 후 로그인
await User.create({
kakao_id: authInfo.data.id,
kakao_access_token: authToken.data.access_token,
userAvatar: authInfo.data.properties.thumbnail_image,
userTitle: '한 줄 소개가 없습니다.',
userName: authInfo.data.properties.nickname,
}).then((user) => {
user.generateToken((error, user) => {
if (error) return res.status(400).send(error);
return res
.cookie('x_auth', user.token, { httpOnly: true })
.status(200)
.redirect(process.env.CLIENT_SERVER);
});
});
}
} catch (error) {
next(error);
}
});
REST API 로그아웃
로그아웃은 카카오계정과 함께 로그아웃 방법을 사용했다.
기존 일반 회원가입 방식으로 로그인한 유저도 있기 때문에 kakaoID가 없다면 일반 로그아웃, 있다면 카카오 로그아웃을 사용하도록 했다.
/* 로그아웃 함수 */
const onClickLogout = useCallback(
(e) => {
e.preventDefault();
if (kakaoId) {
window.location.href = `https://kauth.kakao.com/oauth/logout?client_id=${process.env.KAKAO_REST_API_KEY}&logout_redirect_uri=${process.env.KAKAO_LOGOUT_REDIRECT_URI}`;
} else {
axios
.get(`/api/logout`)
.then(() => {
navigate('/'); // 홈으로 이동
window.location.reload(); // 새로고침
refetch(); // 클라이언트 유저정보 리패치
})
.catch((error) => {
console.log(error);
});
}
},
[refetch, navigate, kakaoId],
);
클라이언트에서 로그아웃 버튼을 클릭하면 로그아웃 웹뷰 페이지로 넘어가게 되고 아래와 같은 로그아웃 확인 페이지가 나온다.
로그아웃을 클릭하게 되면 서버에서 핸들링만 해주면 된다. 쿠키를 삭제하고 홈페이지로 redirect시킨다.
/**
* @path {POST} /api/auth/kakao/logout
* @description 카카오 로그아웃
*/
router.get('/auth/kakao/logout', async (req, res, next) => {
try {
return res.status(200).clearCookie('x_auth').redirect(process.env.CLIENT_SERVER);
} catch (error) {
next(error);
}
});
랜딩페이지 UI 변경
수정 전 랜딩페이지
펼쳐져 있는 레이아웃 때문에 시선이 분산 되고 포스트카드 각각 그림자 때문에 너무 튀어 보였다.
유저리스트도 채팅 기능이 있는 사이트가 아니다 보니 한 구간을 차지하면서 까지 보여줄 필요성을 느끼지 못했다.
수정 후 랜딩페이지
그래서 첫 로드 시 사이트에 대한 설명과 인기/최신 포스트를 세개정도 보여주고 [더보기]를 클릭해서 포스트 페이지로 넘어가도록 했다.
유저리스트는 [공간을 빛내주시는 분들]이라는 레이아웃으로 새로 만들어 프로필 카드로 펼쳐놨다.
물론 접속중이면 처음으로 오게 설정한 건 그대로 해놨다.