diff --git a/components/Auth/SnsLogin.tsx b/components/Auth/SnsLogin.tsx
index 2a4a2a6..d0c8b30 100644
--- a/components/Auth/SnsLogin.tsx
+++ b/components/Auth/SnsLogin.tsx
@@ -6,15 +6,21 @@ const SnsLogin = () => {
);
diff --git a/components/Auth/SnsPassword.tsx b/components/Auth/SnsPassword.tsx
new file mode 100644
index 0000000..cea086b
--- /dev/null
+++ b/components/Auth/SnsPassword.tsx
@@ -0,0 +1,29 @@
+import Image from "next/image";
+import Link from "next/link";
+
+const SnsLogin = () => {
+ return (
+
+
소셜 회원가입
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SnsLogin;
diff --git a/package-lock.json b/package-lock.json
index a01fe10..c930b05 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,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",
@@ -3782,6 +3783,15 @@
"node": ">=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 9ba9f72..6b36999 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..9334068
--- /dev/null
+++ b/pages/api/auth/sign-in/google.ts
@@ -0,0 +1,76 @@
+import axios from "axios";
+import axiosInstance from "@/lib/api/axiosInstanceApi";
+import { NextApiRequest, NextApiResponse } from "next";
+import { serialize } from "cookie";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) => {
+ try {
+ const { code } = req.query;
+ if (!code) {
+ return res.status(400).json({ message: "인증 코드가 없습니다." });
+ }
+
+ const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
+ const clientSecret = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET;
+ const redirectUri =
+ process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_IN ||
+ "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 토큰을 가져오지 못했습니다." });
+ }
+
+ // 이미 회원인지 체크
+ 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) {
+ return res.redirect("/signup");
+ }
+ } 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-in/kakao.ts b/pages/api/auth/sign-in/kakao.ts
new file mode 100644
index 0000000..3389bc3
--- /dev/null
+++ b/pages/api/auth/sign-in/kakao.ts
@@ -0,0 +1,53 @@
+import axiosInstance from "@/lib/api/axiosInstanceApi";
+import { NextApiRequest, NextApiResponse } from "next";
+import { serialize } from "cookie";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) => {
+ try {
+ const { code } = req.query;
+ console.log(code);
+ if (!code) {
+ return res.status(400).json({ message: "인증 코드가 없습니다." });
+ }
+
+ const redirectUri =
+ process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_IN ||
+ "http://localhost:3000/";
+
+ try {
+ const loginResponse = await axiosInstance.post("/auth/sign-in/kakao", {
+ token: code,
+ 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
+ );
+ return res.redirect("signup");
+ }
+ } 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-up/google.ts b/pages/api/auth/sign-up/google.ts
new file mode 100644
index 0000000..b98efd9
--- /dev/null
+++ b/pages/api/auth/sign-up/google.ts
@@ -0,0 +1,85 @@
+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.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
+ const clientSecret = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET;
+ const redirectUri =
+ process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_UP ||
+ "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 토큰을 가져오지 못했습니다." });
+ }
+
+ const userInfo: GoogleUserInfo = jwtDecode(id_token);
+ const { name } = userInfo;
+
+ 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
+ .status(200)
+ .json({ message: "회원가입 성공", redirectUrl: "/" });
+ }
+ } catch (signUpError: any) {
+ return res.redirect("/login");
+ }
+ } 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-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/api/auth/sign-up/kakao.ts b/pages/api/auth/sign-up/kakao.ts
new file mode 100644
index 0000000..628aa3f
--- /dev/null
+++ b/pages/api/auth/sign-up/kakao.ts
@@ -0,0 +1,55 @@
+import axiosInstance from "@/lib/api/axiosInstanceApi";
+import { NextApiRequest, NextApiResponse } from "next";
+import { serialize } from "cookie";
+
+const handler = async (req: NextApiRequest, res: NextApiResponse) => {
+ try {
+ const { code } = req.query;
+ console.log(code);
+ if (!code) {
+ return res.status(400).json({ message: "인증 코드가 없습니다." });
+ }
+
+ const redirectUri =
+ process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_UP ||
+ "http://localhost:3000/";
+
+ // 회원가입 시도
+ try {
+ const signUpResponse = await axiosInstance.post("/auth/sign-up/kakao", {
+ name: "사용자",
+ token: code,
+ redirectUri,
+ });
+
+ 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.redirect("/login");
+ }
+ } 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/google.tsx b/pages/google.tsx
deleted file mode 100644
index 9f0059a..0000000
--- a/pages/google.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import axios from "axios";
-import { useEffect } from "react";
-
-const Google = () => {
- useEffect(() => {
- const hash = window.location.hash;
- const params = new URLSearchParams(hash.substring(1));
- const token = params.get("access_token");
- console.log(token);
-
- axios
- .get(`https://www.googleapis.com/oauth2/v2/userinfo`, {
- headers: { Authorization: `Bearer ${token}` },
- })
- .then((res) => console.log(res));
- }, []);
-
- return 안녕
;
-};
-
-export default Google;
diff --git a/pages/signup/index.tsx b/pages/signup/index.tsx
index cec6cad..864b4da 100644
--- a/pages/signup/index.tsx
+++ b/pages/signup/index.tsx
@@ -3,6 +3,7 @@ import SubmitButton from "@/components/SubMitButton";
import AuthLayout from "@/components/Layout/AuthLayout";
import Link from "next/link";
import useForm from "@/hooks/useForm";
+import SnsPassword from "@/components/Auth/SnsPassword";
const SignupPage = () => {
const { values, errors, handleChange, handleBlur, handleSubmit } =
@@ -68,6 +69,7 @@ const SignupPage = () => {
회원가입
+