์ฌํ ํ ๊ฒ ์ค์ ์ ์ผ ์ด๋ ค์ ๋ค.. ์นด์นด์คํก ๋ก๊ทธ์ธ ์์ฒด๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ์ด๋ ต์ง ์์๋๋ฐ ๊ธฐ์กด์ ๋ก๊ทธ์ธ ํ์ธ ๋ก์ง(๋ ์ฟ ํค๋ก ๋๊ฒจ์ ๊ด๋ฆฌํจ)์ด๋ ์นด์นด์ค ๋ก๊ทธ์ธ ํ์ธ ๋ก์ง์ ํตํฉํ๋ ๋ฐ ์ ๋ฅผ ๋จน์๋ค. ์นดํก ๋ก๊ทธ์ธ ๊ตฌํ์ ๊ฑธ๋ฆฐ ์๊ฐ์ด 1์๊ฐ์ด๋ผ๋ฉด ๋ก๊ทธ์ธ ํ์ธ ๋ก์ง ํตํฉ์ ์ฒด๊ฐ 12์๊ฐ ๊ฑธ๋ฆฐ ๋๋.. ์๋ฌดํผ ์ด๊ฑด ๊ธฐ๋กํ์ง ์์ผ๋ฉด ๋๊ณ ๋๊ณ ํํํ ๊ฒ ๊ฐ์์ ์ค๋๋ง์ ํฌ์คํ ์ ํด๋ณธ๋ค. ๊ฑฐ๋์ ๋ฏธํ๊ณ ์์ํด ๋ณด๊ฒ ๋ค.
1. ์นด์นด์ค ๋ก๊ทธ์ธ ๊ตฌํ
- Kakao Developers ๊ณต์ ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋ฉด ๋ก๊ทธ์ธ๊น์ง ์ด๋ ต์ง ์๊ฒ ๊ตฌํ์ด ๊ฐ๋ฅํ๋ค.(์ฐจ๊ทผ์ฐจ๊ทผ ํ๋ฉด ๋)
1) ํด๋ผ์ด์ธํธ ์ฝ๋(Kakao.jsx)
import React, { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import axios from "axios";
const Kakao = ({ setIsAuthenticated }) => {
const navigate = useNavigate();
useEffect(() => {
const handleKakaoLoginCallback = async () => {
const url = new URL(window.location.href);
const code = url.searchParams.get("code");
if (!code) {
console.log("์นด์นด์ค ๋ก๊ทธ์ธ ํ์ด์ง ๋ ๋๋ง ์ค...");
return;
}
try {
const response = await axios.post(
"http://localhost:5001/auth/kakao/callback",
{ code },
{ withCredentials: true }
);
const jwtToken = response.data.jwtToken;
if (jwtToken) {
axios.defaults.headers.common["authorization"] = `Bearer ${jwtToken}`;
alert("๋ก๊ทธ์ธ ์ฑ๊ณต");
setIsAuthenticated(true);
navigate("/");
} else {
throw new Error("ํ ํฐ ๋ณตํธํ ์คํจ");
}
} catch (err) {
alert("๋ก๊ทธ์ธ ์คํจ");
console.error("Kakao ์ธ์ฆ ์คํจ:", err.message);
navigate("/auth/login");
}
};
handleKakaoLoginCallback();
}, [setIsAuthenticated]);
};
export default Kakao;
- ๊ณต์๋ฌธ์ ๋ฐ๋ผ ๊ฐ๋ค๊ฐ..(~๋ฐฑ์๋๋ก ์ธ์ฆ ์ฝ๋ ์ ์ก๊น์ง)
- withCredentials: true๋ฅผ ์ค์ ํด ์ฟ ํค์ ์ ์ฅ๋ ์ธ์ฆ ์ ๋ณด๋ฅผ ํ์ฉ
- ์๋ฒ๋ก๋ถํฐ JWT ์๋ต์ ๋ฐ๊ณ axios.defaults.headers.common์ Bearer ํ ํฐ์ ์ถ๊ฐํด ์ดํ์ ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ ํค๋๋ฅผ ํฌํจํ๋๋ก ์ค์
- JWT ํ ํฐ์ด ์กด์ฌํ๋ฉด, ์ธ์ฆ ์ฑ๊ณต ๋ฉ์ธ์ง ์ถ๋ ฅํ๊ณ ์ธ์ฆ ์ํ ์ ๋ฐ์ดํธ(setIsAuthenticated(true))ํ ํ ํํ์ด์ง๋ก ์ด๋
- ๊ทธ๋ฆฌ๊ณ ๋ผ์ฐํฐ์ ๊ฒฝ๋ก ์ถ๊ฐ
2) ์๋ฒ ์ฝ๋(kakao.js)
const express = require("express");
const axios = require("axios");
const jwt = require("jsonwebtoken");
const router = express.Router();
require("dotenv").config();
const bodyParser = require("body-parser");
const cookieParser = require("cookie-parser");
router.use(bodyParser.urlencoded({ extended: true }));
router.use(bodyParser.json());
router.use(cookieParser());
const KAKAO_TOKEN_URL = process.env.KAKAO_TOKEN_URL;
const KAKAO_USER_INFO_URL = process.env.KAKAO_USER_INFO_URL;
const JWT_SECRET = process.env.JWT_SECRET;
const KAKAO_CLIENT_ID = process.env.KAKAO_CLIENT_ID;
const REDIRECT_URI = process.env.REDIRECT_URI;
router.post("/callback", async (req, res) => {
const { code } = req.body;
if (!code) {
return res.status(400).send("Kakao code is missing");
}
try {
const tokenResponse = await axios.post(KAKAO_TOKEN_URL, null, {
params: {
grant_type: "authorization_code",
client_id: KAKAO_CLIENT_ID,
redirect_uri: REDIRECT_URI,
code,
},
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
if (!tokenResponse.data || !tokenResponse.data.access_token) {
throw new Error("Failed to get access token");
}
const { access_token } = tokenResponse.data;
const userResponse = await axios.get(KAKAO_USER_INFO_URL, {
headers: { Authorization: `Bearer ${access_token}` },
});
const kakaoUser = userResponse.data;
const email = kakaoUser?.kakao_account?.email || null;
const nickname = kakaoUser?.properties?.nickname || "Unknown";
const user = {
kakaoId: kakaoUser.id,
email,
nickname,
};
const jwtToken = jwt.sign(user, JWT_SECRET, { expiresIn: "1h" });
res.cookie("x_auth", jwtToken, {
httpOnly: true,
maxAge: 3600000,
});
return res.status(200).json({
message: "Kakao login success",
loginSuccess: true,
jwtToken,
user: {
kakaoId: user.kakaoId,
email: user.email,
nickname: user.nickname,
},
});
} catch (err) {
console.error(err);
res.status(500).send("Kakao ์ธ์ฆ ์คํจ");
}
});
module.exports = router;
- ํด๋ผ์ด์ธํธ๋ก๋ถํฐ code๋ฅผ POST ์์ฒญ์ผ๋ก ์ ๋ฌ๋ฐ์ ํ์ธ, ์์ ๊ฒฝ์ฐ 400 ์๋ฌ
- ์นด์นด์ค ์๋ฒ์ ์์ธ์ค ํ ํฐ ์์ฒญํ๊ณ , ๋ฐ์ ํ ํฐ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด ์์ฒญ
- ์ฌ์ฉ์ ์ ๋ณด ๊ธฐ๋ฐ์ผ๋ก JWT ํ ํฐ ์์ฑ
- ์์ฑ๋ JWT ํ ํฐ์ httpOnly ์ฟ ํค๋ก ์ค์
2. ์นด์นด์ค OAuth2 ์ธ์ฆ ๋ฏธ๋ค์จ์ด ๊ตฌํ
- ํด๋ผ์ด์ธํธ์์ ๊ธฐ๋ณธ ๋ก๊ทธ์ธ์ด๋ ์นด์นด์ค ๋ก๊ทธ์ธ์ ํตํฉํด์ ์ธ์ฆ ์ํ๋ฅผ ์ผ๊ด๋๊ฒ ์ ์งํด์ผ ํ๋๋ฐ ๊ทธ๊ฒ ์๋์ ์นด์นด์ค ๋ก๊ทธ์ธํ๋ฉด ๋ก๊ทธ์ธ ์ํ์ธ ๊ฑธ ์ธ์์ ๋ชปํด์ ๋ก๊ทธ์ธ์ด ํ์ํ ๋ค๋ฅธ ํ์ด์ง์ ์ ๊ทผ์ด ์๋๋ ์ด์๊ฐ ์๊ฒผ๋ค.
- ๋ฌธ์ ๋ ๋ฏธ๋ค์จ์ด ๋ถ๋ถ์ด์๋ค.
- ์ฒ์์ ๊ทธ๋ฅ JWT๋ก ๋ง๋ค์ด์ ์ฐ๋ฉด ๊ธฐ์กด์ ๋ก์ง์ ๋ฐ๋ก ์ ์ฉ๋๋ ์ค ์์๋๋ฐ ์๊ณ ๋ณด๋ ๊ทธ ๋ด๋ถ ํํ๋ ๋ค๋ฅด๊ณ ๋ญ๊ฐ ๋ณต์กํ๊ฒ ์ฝํ์์๋ค.
- ๊ทธ๋์ ํ๋ํ๋ ๋ก๊ทธ๋ฅผ ์ฐ์ด๋ณด๋ฉด์ ๋ฏ์ด ๊ณ ์ณ๋ณด์๋ค..(์ด๊ฒ ์ง์ง 10์๊ฐ์ ๋๊ฒ ๊ฑธ๋ฆฐ ๋ฏ..ใ ใ ใ )
1) ์นด์นด์ค ํ ํฐ ๊ฒ์ฆ ํจ์ (verifyKakaoToken)
const verifyKakaoToken = async (token) => {
try {
const response = await axios.post(
"https://kapi.kakao.com/v2/user/me",
{},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
if (response.data) {
return {
isAuth: true,
user: {
_id: response.data.id,
email: response.data.kakao_account.email,
username: response.data.properties.nickname,
},
};
} else {
return { isAuth: false };
}
} catch (error) {
console.error("์นด์นด์ค ํ ํฐ ๊ฒ์ฆ ์๋ฌ:", error);
return { isAuth: false };
}
};
- ์ด ํจ์๋ ํด๋ผ์ด์ธํธ๊ฐ ์ ๋ฌํ ์นด์นด์ค ์ก์ธ์ค ํ ํฐ์ ์ด์ฉํด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฒ์ฆํจ
- ์นด์นด์ค API์ ์์ฒญํด ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ฉฐ, ์ ํจํ์ง ์์ ํ ํฐ์ผ ๊ฒฝ์ฐ ์๋ฌ๋ฅผ ๋ฐํํจ
2) User ๋ชจ๋ธ ์์ (findOrCreateByKakaoId)
userSchema.statics.findOrCreateByKakaoId = async function (kakaoId, userData) {
const kakaoIdStr = kakaoId.toString();
// console.log("์นด์นด์คId ๋ฌธ์์ด ๋ณํ", kakaoIdStr);
const user = await this.findOne({ kakaoId: kakaoIdStr });
// console.log("๋ชจ๋ธ์์ ์ฐพ์ user:", user);
if (user) {
return user;
} else {
// console.log("์ฌ์ฉ์๊ฐ ์กด์ฌํ์ง ์์, ์๋ก์ด ์ฌ์ฉ์ ์์ฑ.");
const newUser = new this({
kakaoId: kakaoIdStr,
user_id: userData.user_id || kakaoIdStr,
email: userData.email,
name: userData.nickname,
profile_image: userData.profile_image || null,
});
return newUser
.save()
.then((savedUser) => {
console.log("์ ์ฅ๋ ์ฌ์ฉ์:", savedUser);
return savedUser;
})
.catch((err) => {
console.error("์ฌ์ฉ์ ์ ์ฅ ์คํจ:", err);
throw new Error("์ฌ์ฉ์ ์ ์ฅ ์คํจ");
});
}
};
- ์ฃผ์ด์ง kakaoId๋ก ์ฌ์ฉ์๋ฅผ ๊ฒ์ํ๊ณ ์์ผ๋ฉด ์๋ก์ด ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ฅผ ์์ฑํจ
3) ์ธ์ฆ ๋ฏธ๋ค์จ์ด ์์ (auth.js)
// ์ธ์ฆ ๋ฏธ๋ค์จ์ด
let auth = async (req, res, next) => {
const jwtToken = req.cookies.x_auth;
const kakaoToken = req.headers.authorization?.split(" ")[1];
const secretKey = process.env.JWT_SECRET;
// JWT ์ธ์ฆ ์ฒ๋ฆฌ
if (jwtToken) {
if (!secretKey) {
return res.status(500).json({
isAuth: false,
message: "์๋ฒ ์ค์ ์ค๋ฅ: ๋น๋ฐ ํค๊ฐ ์ค์ ๋์ง ์์์ต๋๋ค.",
});
}
try {
const decoded = jwt.verify(jwtToken, secretKey);
console.log("decoded", decoded);
// ํ ํฐ ๋ง๋ฃ ํ์ธ
if (decoded.exp < Date.now() / 1000) {
return res.status(401).json({
isAuth: false,
message: "ํ ํฐ์ด ๋ง๋ฃ๋์์ต๋๋ค.",
});
}
let user;
if (decoded.kakaoId) {
// ์นด์นด์ค
const kakaoId = decoded.kakaoId.toString();
user = await User.findOrCreateByKakaoId(kakaoId, {
user_id: decoded.user_id,
email: decoded.email,
nickname: decoded.nickname,
profile_image: decoded.profile_image || null,
});
} else if (decoded._id) {
// ์ผ๋ฐ ์ ์
user = await User.findById(decoded._id);
} else {
return res.status(401).json({
isAuth: false,
message: "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.",
});
}
// console.log("user ํ์ธ", user);
if (!user) {
return res.status(401).json({
isAuth: false,
message: "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.",
});
}
req.token = jwtToken;
req.user = user;
return next();
} catch (err) {
console.error("JWT ๊ฒ์ฆ ์คํจ:", err);
return res.status(401).json({
isAuth: false,
message: "์ ํจํ์ง ์์ ํ ํฐ์
๋๋ค.",
});
}
}
// ์นด์นด์ค ๋ก๊ทธ์ธ ์ธ์ฆ ์ฒ๋ฆฌ
if (kakaoToken) {
const kakaoResult = await verifyKakaoToken(kakaoToken);
if (kakaoResult.isAuth) {
req.user = kakaoResult.user;
return next();
} else {
return res.status(401).json({
isAuth: false,
message: "์นด์นด์ค ์ธ์ฆ ์คํจ",
});
}
}
// JWT์ ์นด์นด์ค ํ ํฐ ๋ชจ๋ ์์ ๊ฒฝ์ฐ
console.error("์ฟ ํค ๋๋ ํค๋์์ ์ธ์ฆ ํ ํฐ์ด ๊ฐ์ง๋์ง ์์");
return res.status(401).json({
isAuth: false,
message: "๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค.",
});
};
- ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ๋ฐ์ JWT ๋๋ ์นด์นด์ค ํ ํฐ์ ๊ฒ์ฆํ๊ณ ์ฌ์ฉ์ ์ ๋ณด๋ฅด req.user์ ์ ์ฅํ๋ ๋ฏธ๋ค์จ์ด
- ๊ฐ์ฅ! ์ด๋ ค์ ๋ ๋ถ๋ถ์ธ๋ฐ, ์ผ๋ฐ ๋ก๊ทธ์ธ๊ณผ ์นด์นด์ค ๋ก๊ทธ์ธ์ ๊ตฌ๋ณํด์ผ ํ๋ ์ ์ด ์ด๋ ค์ ์
- ๋ฌด์๋ณด๋ค ์นด์นด์ค API ํธ์ถํ์๋ ์๋ต ๋ฐ์ดํฐ ํ์ฑ์ด ์ ๋๋ก ์ด๋ค์ง์ง ์์์ undefined ์ค๋ฅ๊ฐ ๊ณ์ ๋ฐ์ํจ
3. ๋๋ ์
- ์ฝ๊ฐ ์ฐ๊ณ ๋๋๊น ๋ณ๊ฑฐ ์๋ ๊ฑฐ ๊ฐ๋ค.. ์ฉ ์ด๋ฏธ ์ฝ๋ ํ๋ฆ์ด ๋จธ๋ฆฟ์ ์ ์์ด์ ๊ทธ๋ฐ ๊ฒ ๊ฐ์. ์ ๋๋ก๋ ํธ๋ฌ๋ธ ์ํ ์ ์จ๋ณด๊ณ ์ถ์๋๋ฐ ์์ฝ๋ค.
- ์์ผ๋ก๋ Redis๋ ์จ์ ์ธ์ ๊ด๋ฆฌ๋ฅผ ์ ๋๋ก ํด๋ณด๊ณ ์ถ๋ค.