Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions components/Auth/SnsLogin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@ const SnsLogin = () => {
<div className="flex items-center justify-between bg-gray300 rounded-lg px-6 py-3 mt-8">
<span>소셜 로그인</span>
<div className="flex gap-4">
<Link href="https://accounts.google.com/o/oauth2/v2/auth?scope=openid%20profile%20email&response_type=token&redirect_uri=http://localhost:3000/google&client_id=1079911783112-7rg5ecp9ia9lorm7pit0m2nb2ti1rpt0.apps.googleusercontent.com">
<Link
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_IN}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}`}
>
<Image src="/icons/google.svg" width="42" height="42" alt="구글" />
</Link>
<Image
src="/icons/kakaotalk.svg"
width="42"
height="42"
alt="카카오톡"
/>
<Link
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_IN}&response_type=code`}
>
<Image
src="/icons/kakaotalk.svg"
width="42"
height="42"
alt="카카오톡"
/>
</Link>
</div>
</div>
);
Expand Down
29 changes: 29 additions & 0 deletions components/Auth/SnsPassword.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import Image from "next/image";
import Link from "next/link";

const SnsLogin = () => {
return (
<div className="flex items-center justify-between bg-gray300 rounded-lg px-6 py-3 mt-8">
<span>소셜 회원가입</span>
<div className="flex gap-4">
<Link
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_UP}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}`}
>
<Image src="/icons/google.svg" width="42" height="42" alt="구글" />
</Link>
<Link
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_UP}&response_type=code`}
>
<Image
src="/icons/kakaotalk.svg"
width="42"
height="42"
alt="카카오톡"
/>
</Link>
</div>
</div>
);
};

export default SnsLogin;
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
76 changes: 76 additions & 0 deletions pages/api/auth/sign-in/google.ts
Original file line number Diff line number Diff line change
@@ -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;
File renamed without changes.
53 changes: 53 additions & 0 deletions pages/api/auth/sign-in/kakao.ts
Original file line number Diff line number Diff line change
@@ -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;
85 changes: 85 additions & 0 deletions pages/api/auth/sign-up/google.ts
Original file line number Diff line number Diff line change
@@ -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;
File renamed without changes.
55 changes: 55 additions & 0 deletions pages/api/auth/sign-up/kakao.ts
Original file line number Diff line number Diff line change
@@ -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;
21 changes: 0 additions & 21 deletions pages/google.tsx

This file was deleted.

Loading
Loading