๐Ÿ› ๏ธ ํ”„๋กœ์ ํŠธ/[workRoot] - ์•Œ๋ฐ” ํ”Œ๋žซํผ

[Google ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ] Next14 + App router + Typescript์— Oauth ๊ตฌ๊ธ€ ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ๊ตฌํ˜„ํ•˜๊ธฐ

yyezzzy 2024. 12. 4. 13:59

๊ตฌ๊ธ€ ๊ฐ„ํŽธ ๋กœ๊ทธ์ธ ํ•˜๋Š” ๋ฒ•

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 ๋””์ฝ”๋”ฉ ์‚ฌ์ดํŠธ ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.