passport-kakao
를 이용해서 구현했습니다.
위 4가지를 등록한 후에 REST API KEY
를 이용해서 카카오에 로그인 및 로그아웃 요청을 보낼 수 있습니다.
REST API KEY
를 아래의 clinetID
에 넣어주면 카카오톡에서 확인 후 토큰들과 유저가 허용한 데이터를 보내줍니다.
해당 데이터와 토큰을 가지고 유저를 인증하는데 사용하면 됩니다.
accessToken
은 추후에 로그아웃하는데 사용하게 됩니다.
import passport from "passport";
import { Strategy as KakaoStrategy } from "passport-kakao";
import db from "../models/index.js";
const { User, Image } = db;
const passportKakaoConfig = () => passport.use(
new KakaoStrategy(
{
clientID: process.env.KAKAO_KEY,
callbackURL: "/auth/kakao/callback"
},
async (accessToken, refreshToken, profile, done) => {
const { id: snsId, provider, username: name } = profile;
const { profile_image_url: url } = JSON.parse(profile._raw).kakao_account.profile;
try {
const exUser = await User.findOne({
where: {
snsId,
provider,
name,
},
});
if(exUser){
// 프로필 이미지가 변경될 가능성이 높으니 로그인할 때마다 새로 업데이트
await Image.update(
{ url },
{
where: {
UserId: exUser._id
}
}
);
return done(null, { user: exUser, accessToken });
} else {
const createdUser = await User.create({
snsId,
provider,
name,
});
await Image.create({
url,
UserId: createdUser._id
});
return done(null, { user: createdUser, accessToken });
}
} catch (error) {
done(error);
}
}
)
);
export default passportKakaoConfig;
// 카카오 로그인
router.get("/kakao", isNotLoggedIn, passport.authenticate("kakao"));
router.get("/kakao/callback", passport.authenticate("kakao", {
failureRedirect: "/"
}), (req, res) => {
res.redirect(process.env.CLIENT_URL);
});
// 로그아웃
router.delete("/", isLoggedIn, async (req, res, next) => {
if(req.user.accessToken){
try {
await axios.post("https://kapi.kakao.com/v1/user/unlink", null, {
headers: {
"Authorization": `Bearer ${req.user.accessToken}`,
}
});
} catch (error) {
return next(error)
}
}
req.logout();
req.session.destroy();
res.status(200).json({ message: "로그아웃에 성공했습니다." });
});
<a>
를 이용해서 서버의 /auth/kakao
로 접근passport.authenticate("kakao")
실행REST API KEY
와 callbackURL
을 카카오로 전송해 주고 응답을 받음 ( 토큰들과 유저정보 )DB
에 유저 생성하고 accessToken
을 세션에 저장local
전략과 같으므로 생략여기서 특이한 점 중 한 가지는 클라이언트에서는 <a>
를 이용해서 서버로 접근하는 것입니다.
이유는 redirect 요청이 올 때 cors
문제가 발생하기 때문에 <a>
로 접근했습니다.
( 사실 정확한 원인은 아직 이해하지 못했고 구글링으로 알아낸 방법으로 해결했습니다. )
두 번째 특이점은 accessToken
를 DB
가 아닌 세션에 저장하는 것입니다.
세션은 서버 측 자원이기 때문에 각 유저마다 저장하면 기하급수적으로 메모리 사용량이 늘어날 수 있지만 많은 메모리를 사용하진 않고, 추후에 redis
로 대체할 생각이기 때문에 큰 문제는 없다고 판단했습니다.
여기에서 css를 얻어서 Spinner
컴포넌트를 만들어서 Button
컴포넌트에 loading
props가 true
면 스피너를 보여주도록 구현했습니다.
로그아웃을 구현할 때는 고급설정에서 로그아웃 URI 등록해야 합니다.
또한 https://kapi.kakao.com/v1/user/unlink
or https://kapi.kakao.com/v1/user/logout
에 요청을 보내야 하고,
"Authorization": "Bearer " + req.user.accessToken
와 같은 형태로 헤더에 토큰 값을 실어서 보내줘야 합니다.
카카오 로그인 버튼 같은 경우에는 redux-saga
를 거치지 않고 <a>
를 이용해서 요청하므로 로그인 요청의 시작과 끝, 성공과 실패를 파악할 변수가 없어서 어떻게 spinner
를 적용할지 고민해 보고 있습니다.