๊ตฌ๊ธ ๊ฐํธ ๋ก๊ทธ์ธ ํ๋ ๋ฒ
1. ๊ตฌ๊ธ ํด๋ผ์ฐ๋ ์ฌ์ดํธ ์ ์
ํ๊ตญ์ด ์์ ๋ณด์ด๋ ์ฝ์๋งํฌ๋ก ์ด๋ํฉ๋๋ค.
2. ์ ํ๋ก์ ํธ ์์ฑ
์ ํ๋ก์ ํธ๋ฅผ ์์ฑ ํฉ๋๋ค.
ํ๋ก์ ํธ๊ฐ ๋ง๋ค๊ณ ํด๋น ํ๋ก์ ํธ๋ก ์ด๋ํฉ๋๋ค.
์์ ๋ณด์ด๋ ๊ฒฝ๋ก๋ก OAuth ๋์ ํ๋ฉด์ ์ฐพ์๊ฐ๋๋ค.
3. OAuth ๋์
์ธ๋ถ๋ฅผ ์ ํํ์ฌ ๋ง๋ค๊ธฐ.
์ ์งํ๋จ๊ณ๋ก ์งํํฉ๋๋ค.
์ฑ์ด๋ฆ, ์ฌ์ฉ์ ์ด๋ฉ์ผ, ์ฑ ๋๋ฉ์ธ์ ์ ๋ ฅํฉ๋๋ค. ์ฑ ๋๋ฉ์ธ์ ๊ฐ๋ฐ ์ํ์์ http://localhost:3000/๋ก ์์ฑํ๋ฉด ๋ฉ๋๋ค.
4. ๋ฒ์
์ฌ์ฉ์๊ฐ ๋์ํด์ผ ํ ๊ถํ๋ค์ ์ค์ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๊ตฌ๊ธ ๊ณ์ ์ ๋ณด ์ ๊ทผ์ ์ํ๋ฉด email, profile ๋ฑ์ ๋ฒ์๋ฅผ ์ถ๊ฐํฉ๋๋ค.
๊ถํ์ ์ถ๊ฐํ๋ ค๋ฉด, ํ์ํ ๊ตฌ๊ธ API ๋ฒ์๋ฅผ ์ ํํด์ผ ํฉ๋๋ค.
์ฌ๊ธฐ์ ์ ํํ ๋ฒ์์ ๋ฐ๋ผ google์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์์ฒญํ ์ ์๋ค.
<Link
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}`}
>
<Image src="/icons/social/social_google.svg" width={72} height={72} alt="๊ตฌ๊ธ ๋ก๊ทธ์ธ" />
</Link>
5. ๋ง๋ฌด๋ฆฌ
ํ ์คํธ ์ฌ์ฉ์์ ์์ฝ์ ๋ณ๋ค๋ฅธ ์ฌํญ์ด ์์ผ๋ฉด ๋ค์์ผ๋ก ๋๊ฒจ ์ฑ ๋ฑ๋ก์ ์๋ฃํฉ๋๋ค.
OAuth ํด๋ผ์ด์ธํธ ID ์ค์
์์ ๋ณด์ด๋ ๊ฒฝ๋ก๋ก ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์ฐพ์๊ฐ๋๋ค.
1. OAuth ํด๋ผ์ด์ธํธ ID ์ ํ
์ ๋ด์ฉ๋๋ก ๊ฐ์ ํ๋ก์ ํธ์ ์๋ง์ Redirect URL์ ์ ์ด์ค๋ค.
์ด๋ ๊ฒ ์์ฑ๋ ํด๋ผ์ด์ธํธ ID์ ๋น๋ฐ๋ฒํธ๋ ๋ณต์ฌํ์ฌ .env ํ์ผ์ ์ ์ฅํ์ฌ ์ฌ์ฉํ๋ค.
์ฝ๋ ์์ฑ
Redirect URL ์ฃผ์์ธ BASEURL/api/oauth/callback/google ์ ์ฝ๋๋ ๋ค์๊ณผ ๊ฐ์ด ์์ฑํ์๋ค.
import { NextRequest, NextResponse } from "next/server";
import axios from "axios";
import { decodeJwt } from "@/middleware";
export const GET = async (req: NextRequest) => {
const searchParams = req.nextUrl.searchParams;
const code = searchParams.get("code");
if (!code) {
return NextResponse.json({ message: "Code not found" }, { status: 400 });
}
const GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
const clientSecret = process.env.NEXT_PUBLIC_GOOGLE_SECRET;
const redirectUri = process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI;
if (!clientId || !clientSecret || !redirectUri) {
return NextResponse.json({ message: "Environment variables not set" }, { status: 500 });
}
try {
// Access Token ์์ฒญ
const tokenResponse = await axios.post(GOOGLE_TOKEN_URL, null, {
params: {
code,
client_id: clientId,
client_secret: clientSecret,
redirect_uri: redirectUri,
grant_type: "authorization_code",
},
});
const { access_token, id_token } = tokenResponse.data;
console.log("Google access token:", access_token);
console.log("Google id token:", id_token);
// id_token ๋์ฝ๋ฉ
const decodedIdToken = decodeJwt(id_token);
if (!decodedIdToken) {
return NextResponse.json({ message: "Invalid ID token" }, { status: 400 });
}
const user = {
id: decodedIdToken.sub,
name: decodedIdToken.name,
picture: decodedIdToken.picture,
email: decodedIdToken.email,
};
console.log("Google user:", user);
// ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํด๋ผ์ด์ธํธ์ ๋ฐํ
const response = NextResponse.redirect("http://localhost:3000");
response.cookies.set("user", JSON.stringify(user), {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 60 * 60 * 24, // 1์ผ
path: "/",
});
return response;
} catch (error) {
console.error("Google login error:", error);
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
}
};
์ฝ์๋ก ์ถ๋ ฅํด๋ณธ ๊ตฌ๊ธ ์์ธ์ค ํ ํฐ, ๊ตฌ๊ธ id ํ ํฐ, user๊ฐ ์ ์ถ๋ ฅ๋๋ค.
๊ตฌ๊ธ id ํ ํฐ์ ์ํธํ ๋ JWT ํ ํฐ์ผ๋ก ๋ฐ๊ธ์ด๋์ด JWT ๋์ฝ๋ฉ ์ฌ์ดํธ ์์ ํ์ธํ ์ ์๋ค.