diff --git a/components/Auth/SnsLogin.tsx b/components/Auth/SnsLogin.tsx index 2a4a2a6..f5b1d64 100644 --- a/components/Auth/SnsLogin.tsx +++ b/components/Auth/SnsLogin.tsx @@ -6,7 +6,9 @@ const SnsLogin = () => {
소셜 로그인
- + 구글 =4.0" } }, + "node_modules/jwt-decode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", + "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/package.json b/package.json index e60463b..eacb036 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "dependencies": { "axios": "^1.7.7", "cookie": "^1.0.1", + "jwt-decode": "^4.0.0", "next": "15.0.2", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/pages/api/auth/sign-in/google.ts b/pages/api/auth/sign-in/google.ts new file mode 100644 index 0000000..e68df2c --- /dev/null +++ b/pages/api/auth/sign-in/google.ts @@ -0,0 +1,124 @@ +import axios from "axios"; +import axiosInstance from "@/lib/api/axiosInstanceApi"; +import { NextApiRequest, NextApiResponse } from "next"; +import { serialize } from "cookie"; +import { jwtDecode } from "jwt-decode"; + +interface GoogleUserInfo { + name: string; +} + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + try { + const { code } = req.query; + if (!code) { + return res.status(400).json({ message: "인증 코드가 없습니다." }); + } + + const clientId = process.env.GOOGLE_CLIENT_ID; + const clientSecret = process.env.GOOGLE_CLIENT_SECRET; + const redirectUri = + process.env.GOOGLE_REDIRECT_URI || "http://localhost:3000/"; + + if (!clientId || !clientSecret) { + return res + .status(500) + .json({ message: "Google API 클라이언트 정보가 설정되지 않았습니다." }); + } + + // 토큰 요청 + const tokenUrl = "https://oauth2.googleapis.com/token"; + const params = { + code: code as string, + client_id: clientId, + client_secret: clientSecret, + redirect_uri: redirectUri, + grant_type: "authorization_code", + }; + + const tokenResponse = await axios.post(tokenUrl, params); + const { id_token } = tokenResponse.data; + if (!id_token) { + return res + .status(401) + .json({ message: "ID 토큰을 가져오지 못했습니다." }); + } + + // Google ID 토큰에서 사용자 정보 추출 + const userInfo: GoogleUserInfo = jwtDecode(id_token); + const { name } = userInfo; + console.log(name); + + // 이미 회원인지 체크 + try { + const loginResponse = await axiosInstance.post("/auth/sign-in/google", { + token: id_token, + redirectUri, + }); + + const accessToken = loginResponse.data.access_token; + if (accessToken) { + res.setHeader( + "Set-Cookie", + serialize("accessToken", accessToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + maxAge: 60 * 60 * 24, + path: "/", + }) + ); + return res.redirect("http://localhost:3000"); + } + } catch (loginError: any) { + console.error( + "로그인 실패:", + loginError.response?.data || loginError.message + ); + + // 로그인 실패 시 회원가입 시도 + try { + const signUpResponse = await axiosInstance.post( + "/auth/sign-up/google", + { + name: name || "사용자", + token: id_token, + redirectUri: "http://localhost:3000", + } + ); + + const accessToken = signUpResponse.data.access_token; + if (accessToken) { + res.setHeader( + "Set-Cookie", + serialize("accessToken", accessToken, { + httpOnly: true, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + maxAge: 60 * 60 * 24, + path: "/", + }) + ); + return res.redirect("http://localhost:3000"); + } + } catch (signUpError: any) { + console.error( + "회원가입 실패:", + signUpError.response?.data || signUpError.message + ); + return res.status(500).json({ + message: "회원가입 중 오류가 발생했습니다.", + error: signUpError.response?.data || signUpError.message, + }); + } + } + } catch (error: any) { + console.error("Error:", error.response?.data || error.message); + return res.status(500).json({ + message: "서버 오류", + error: error.response?.data || error.message, + }); + } +}; + +export default handler; diff --git a/pages/api/auth/sign-in.ts b/pages/api/auth/sign-in/index.ts similarity index 100% rename from pages/api/auth/sign-in.ts rename to pages/api/auth/sign-in/index.ts diff --git a/pages/api/auth/sign-up.ts b/pages/api/auth/sign-up/index.ts similarity index 100% rename from pages/api/auth/sign-up.ts rename to pages/api/auth/sign-up/index.ts diff --git a/pages/favorite/index.tsx b/pages/favorite/index.tsx index c54b44c..662ced2 100644 --- a/pages/favorite/index.tsx +++ b/pages/favorite/index.tsx @@ -1,8 +1,9 @@ import { GetServerSideProps, GetServerSidePropsContext } from "next"; -import { proxy } from "@/lib/api/axiosInstanceApi"; +import axiosInstance, { proxy } from "@/lib/api/axiosInstanceApi"; import LinkCard from "@/components/LinkCard"; import CardsLayout from "@/components/Layout/CardsLayout"; import Container from "@/components/Layout/Container"; +import { parse } from "cookie"; interface FavoriteDataType { id: number; @@ -24,13 +25,11 @@ export const getServerSideProps: GetServerSideProps = async ( ) => { // 클라이언트의 쿠키 가져오기 const { req } = context; - const cookies = req.headers.cookie || ""; - + const cookies = parse(req.headers.cookie || ""); + const accessToken = cookies.accessToken; try { - const res = await proxy.get("/api/favorites", { - headers: { - Cookie: cookies, - }, + const res = await axiosInstance.get("/favorites?page=1&pageSize=10", { + headers: { Authorization: `Bearer ${accessToken}` }, }); const { list, totalCount } = res.data || { list: [], totalCount: 0 }; diff --git a/pages/google.tsx b/pages/google.tsx index 9f0059a..25dce71 100644 --- a/pages/google.tsx +++ b/pages/google.tsx @@ -1,3 +1,4 @@ +import { proxy } from "@/lib/api/axiosInstanceApi"; import axios from "axios"; import { useEffect } from "react"; @@ -5,17 +6,50 @@ const Google = () => { useEffect(() => { const hash = window.location.hash; const params = new URLSearchParams(hash.substring(1)); - const token = params.get("access_token"); - console.log(token); + const idToken = params.get("id_token"); // Google에서 전달받은 id_token + const accessToken = params.get("access_token"); // Google에서 전달받은 id_token - axios - .get(`https://www.googleapis.com/oauth2/v2/userinfo`, { - headers: { Authorization: `Bearer ${token}` }, - }) - .then((res) => console.log(res)); + if (accessToken) { + console.log(" Access Token:", accessToken); + proxy + .post("/api/auth/sign-up/google", { + name: "박문균", + token: accessToken, + redirectUri: "http://localhost:3000/google", + }) + .then((response) => { + console.log("Authentication Success:", response.data); + // 성공적인 로그인 처리 후, 리다이렉션 등의 작업을 할 수 있습니다. + }) + .catch((error) => { + console.error("Authentication Error:", error); + }); + } else { + console.error("Access Token이 없습니다."); + } + + if (idToken) { + console.log("Id Token:", idToken); + + // 서버로 id_token을 전달하여 인증을 요청합니다. + proxy + .post("/api/auth/sign-in/google", { + token: idToken, + redirectUri: "http://localhost:3000/google", + }) + .then((response) => { + console.log("Authentication Success:", response.data); + // 성공적인 로그인 처리 후, 리다이렉션 등의 작업을 할 수 있습니다. + }) + .catch((error) => { + console.error("Authentication Error:", error); + }); + } else { + console.error("Id Token이 없습니다."); + } }, []); - return
안녕
; + return
Google OAuth 로그인 완료
; }; export default Google;