
2025.4.9 수요일의 공부기록
이 글에서는 Next.js 환경에서 GitHub 계정을 이용해 로그인(OAuth)을 구현하는 방법을 단계별로 상세히 설명한다.
GitHub 로그인을 구현하려면 먼저 GitHub에 OAuth 앱을 등록해야 한다.
| 항목 | 예시 |
|---|---|
| Application name | my-app (자유롭게 설정) |
| Homepage URL | http://localhost:3000 |
| Authorization callback URL | http://localhost:3000/github/complete |
등록이 완료되면 Client ID와 Client Secret을 발급받게 된다.
이 값은 .env 파일에 다음과 같이 저장하여 안전하게 관리한다.
GITHUB_CLIENT_ID=발급받은_클라이언트_ID
GITHUB_CLIENT_SECRET=발급받은_클라이언트_SECRET
GitHub OAuth는 앱에 허용할 권한(scope)을 설정할 수 있다.
자주 쓰이는 권한은 다음과 같다.
| Scope | 설명 |
|---|---|
read:user | 사용자 프로필 읽기 |
user:email | 사용자의 이메일 주소 접근 |
repo | 사용자의 저장소 접근 |
전체 OAuth scope 목록을 참조하여 필요한 권한만 설정하도록 한다.
OAuth 로그인 흐름은 다음과 같은 순서로 진행된다.
code)를 보낸다.Next.js의 Route Handlers를 활용하면 서버측에서 손쉽게 OAuth 처리를 구현할 수 있다.
HTTP 메서드(GET, POST 등)를 다루는 별도의 API 엔드포인트를 설정한다.
/github/start (로그인 시작)app/github/start/route.ts
export function GET() {
const baseUrl = "https://github.com/login/oauth/authorize";
const params = {
client_id: process.env.GITHUB_CLIENT_ID!,
scope: "read:user user:email",
allow_signup: "true",
};
const formattedParams = new URLSearchParams(params).toString();
const finalUrl = `${baseUrl}?${formattedParams}`;
return Response.redirect(finalUrl);
}
/github/complete (로그인 완료 후 처리)app/github/complete/route.ts
import db from "@/lib/db";
import { getSession } from "@/lib/session";
import { notFound, redirect } from "next/navigation";
import { NextRequest } from "next/server";
export async function GET(request: NextRequest) {
const code = request.nextUrl.searchParams.get("code");
if (!code) {
return notFound();
}
// 액세스 토큰 요청
const accessTokenParams = new URLSearchParams({
client_id: process.env.GITHUB_CLIENT_ID!,
client_secret: process.env.GITHUB_CLIENT_SECRET!,
code,
}).toString();
const accessTokenURL = `https://github.com/login/oauth/access_token?${accessTokenParams}`;
const accessTokenResponse = await fetch(accessTokenURL, {
method: "POST",
headers: { Accept: "application/json" },
});
const { error, access_token } = await accessTokenResponse.json();
if (error) {
return new Response("OAuth 인증 오류 발생", { status: 400 });
}
// 사용자 프로필 정보 요청
const userProfileResponse = await fetch("https://api.github.com/user", {
headers: {
Authorization: `Bearer ${access_token}`,
},
});
const { id, avatar_url, login } = await userProfileResponse.json();
// GitHub 사용자 ID로 기존 사용자 조회
let user = await db.user.findUnique({
where: { github_id: id.toString() },
select: { id: true },
});
// 신규 가입인 경우
if (!user) {
user = await db.user.create({
data: {
github_id: id.toString(),
avatar: avatar_url,
username: login, // 중복 방지를 위한 별도의 처리 필요 (ex. 고유한 접두사 추가)
},
select: { id: true },
});
}
// 세션 생성 후 로그인 처리
const session = await getSession();
session.userId = user.id;
await session.save();
return redirect("/profile");
}
OAuth 인증 코드를 재사용하거나 시간이 지나 만료된 코드를 사용할 경우 다음과 같은 오류가 발생한다.
{
"error": "bad_verification_code",
"error_description": "The code passed is incorrect or expired."
}
이 오류를 해결하려면 다시 GitHub OAuth 프로세스를 시작하여 새로운 인증 코드를 받아와야 한다.
Next.js 15부터 fetch의 기본 캐시 옵션이 변경되었다.
force-cache (캐싱됨)no-store (캐싱되지 않음)따라서 별도 캐싱 옵션을 설정하지 않아도 최신 데이터를 즉시 받아오게 된다.