진행중인 사이드 프로젝트 'Tindux'에 소셜 로그인 기능을 추가했다. 리액트 관련 라이브러리와 카카오 개발자 사이트의 공식문서 덕분에 카카오 API를 통한 카카오계정 인증까지는 빠르게 구현 했지만, 인증 후 사이드 프로젝트 앱 의 계정 및 JWT 인증 시스템과 어떻게 연결시켜야할지에 대해 고민하느라 삽질을 많이 했었다.
먼저 kakao developers에서 애플리케이션 등록을 해준뒤, 플랫폼 설정 탭으로 이동해서 사이트 도메인을 설정해줬다. (아직 개발 단계이므로 http://localhost:3000 으로 설정해줬다.)
그 후 앱 키 탭에서 사용할 플랫폼의 키를 복사해주자. (리액트에서 사용할 것 이므로 Java Script키를 복사해 주었다.)
npm install react-kakao-login
import React from 'react';
import styled from 'styled-components';
import KaKaoLogin from 'react-kakao-login';
const buttonBlock = {
border: 'none',
borderRadius: '9px',
fontSize: '17px',
width: '284px',
fontWeight: '500',
height: '32px',
cursor: 'pointer',
background: '#fae101',
alignItems: 'center',
display: 'flex',
justifyContent: 'center',
padding: '4px 0px',
};
const ButtoninnerText = styled.h3`
margin: 0;
font-size: 14px;
`;
const Kakao = ({ oAuthLoginHandler }) => {
return (
<>
<KaKaoLogin
token={KAKAO_JAVASCRIPT_KEY}
buttonText="kakao"
onSuccess={oAuthLoginHandler}
onFail={console.error}
onLogout={console.info}
style={buttonBlock}
>
<ButtoninnerText>카카오 계정으로 로그인</ButtoninnerText>
</KaKaoLogin>
</>
);
};
export default Kakao;
react-kakao-login
라이브러리 덕분에 짧은 코드로 인증기능을 구현할 수 있었다.token
엔 사이트에서 복사해온 자바스크립트 키를 넣어주고, onSuccess엔 인증 완료 후 도착하는 유저 정보 객체를 받는 콜백을 지정할 수 있다.
로그인 버튼 클릭 시 동의 여부를 묻는 카카오 팝업이 뜨며, 동의 후엔 해당 앱과 로그인할 카카오 계정이 연결된 뒤 해당 유저 정보가 담긴 객체를 응답해준다. 객체엔 access_token과 재 인증을 위한 refresh_token, 그리고 유저의 이메일, 닉네임, 프로필 사진 등등 여러 정보가 담겨져 있다.
먼저 로그인과 관련된 모든 데이터 흐름을 제어하는 LoginContainer 컴포넌트에 oAuthLoginHandler
라는 함수를 만들어 줬다. 해당 함수는 기존 방식으로 로그인 submit시 호출되는 onSubmitHandler
함수 (스크린샷의 26번 라인)와 상당히 흡사한데, 구조 분해 할당 문법으로 인자로 전달될 카카오 측의 응답 객체의 id와 email을 가져온 뒤 request객체에 담아서 리덕스 사가의 loginUser 액션으로 전달되고 사가 함수에 의해 서버의 로그인 api로 POST요청을 보낸다.
우선 위 스크린샷을 보면 oAuthId
라는 키에 값으로 id를 지정해준것을 볼 수 있다. 여기서 id는 카카오측에서 제공해주는 유저 객체에 Number타입으로 담겨져있는 고유한 카카오 계정 id인데, 일반 계정과 소셜 계정의 구분을 위해서 email, password로 요청을 보내는 일반 로그인 방식과는 다르게 oAuthId, email로 요청을 보내게 된다.
import express from "express";
import User from "../../../models/User"; //Mongo DB의 User 모델을 불러온다.
const router = express.Router();
router.post("/", (req, res) => {
// 소셜 로그인 시
if (req.body.oAuthId) { //요청 body에 oAuthId 키가 존재하는지 체크한다.
//만일 존재한다면, DB에 해당 oAuthId를 갖고있는 유저를 탐색한다.
User.findOne({ oAuthId: req.body.oAuthId }, (err, user) => {
if (!user) {
const userSchema = new User(req.body);
// 계정 생성
userSchema.save((err, _) => {
if (err) return res.json({ success: false, err });
return res.status(200).json({
registerSuccess: true,
});
});
}
//JWT 토큰 발급
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
// save Token at Cookie
res
.cookie("x_auth", user.token) //쿠키에 JWT토큰을 넣어준다.
.status(200)
.json({ loginSuccess: true, userId: user._Id, token: user.token });
});
});
return;
} else { // 일반 로그인 시
//Find Email at DB
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "해당 이메일에 해당하는 유저가 없습니다.",
});
}
//Check Password
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch)
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다.",
});
//Make Token
user.generateToken((err, user) => {
if (err) return res.status(400).send(err);
//save Token at Cookie
res
.cookie("x_auth", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._Id, token: user.token });
});
});
});
}
});
export default router;
간단하게 정리하자면 로그인 API로 POST 요청 시 request body에
oAuthId
키가 존재하는지 체크한다. 만일 존재한다면, DB에 해당 oAuthId를 갖고있는 유저를 탐색한다. 만일 존재하지 않는다면, 계정을 생성한 뒤 JWT토큰을 발급하여 쿠키에 넣어준다.
토큰 발급까지 마친뒤엔 리덕스 사가의 유저 인증과 관련된 로직에 의해 서버의 auth API로 요청을 보내게 되고, 해당 API는 클라이언트의 쿠키에서 토큰을 통해 유효성 검사를 한뒤, 결과 여부에 따라 유저의 정보를 응답해준 뒤 최종적으론 로그인 및 인증 성공 후엔 리듀서에 유저 정보가 담기게 된다.
소셜로그인 구현중이었는데 정리가 정말 잘되었어서 도움이 많이 됐습니다.
혹시 일반계정과 소셜계정을 나눈 이유가 따로 있으실까요?