Next.js OAuth 로그인 완벽 가이드: Google, GitHub, WeChat 소셜 로그인 설정과 모범 사례

‘Google로 로그인’을 클릭하면 Google 페이지로 이동하고, 승인을 누르면 다시 돌아와 자동으로 로그인이 완료됩니다.
누구나 수없이 겪어 본 과정일 겁니다. 그런데 막상 프로젝트에 소셜 로그인을 추가하려고 하면 갑자기 막막해집니다. 왜 두 번이나 이동해야 할까요? 콜백 주소란 무엇일까요? redirect_uri_mismatch는 또 무슨 뜻일까요? 로컬 테스트에서는 정상인데 온라인에 배포하면 왜 오류가 날까요?
처음 OAuth를 설정했을 때 저도 문서를 많이 읽었습니다. 화면에는 ‘인증 코드’, ‘access_token’, ‘client_secret’ 같은 용어가 가득했고, 읽을수록 더 혼란스러웠습니다. 결국 이틀 동안 수많은 문제를 겪고 나서야 겨우 이해할 수 있었습니다.
이 글에서는 OAuth 로그인을 최대한 쉽게 설명하려고 합니다. 용어를 늘어놓거나 어려운 이론을 말하는 대신, OAuth가 실제로 어떻게 작동하는지, 그리고 Next.js에서 Google, GitHub, WeChat 로그인을 설정하는 방법을 살펴봅니다. 끝까지 읽고 나면 생각보다 그렇게 어렵지 않다는 것을 알게 될 겁니다.
OAuth 2.0이란 무엇인가요? 쉽게 이해하기
일상적인 예로 먼저 이해해 봅시다
아파트에 살고 있다고 가정해 봅시다. 어느 날 온라인으로 물건을 주문했고, 택배 기사가 집 앞까지 배송해 주기를 원합니다. 그런데 아파트에 출입 통제 시스템이 있어 택배 기사가 들어올 수 없습니다.
전통적인 방법은 출입 카드를 택배 기사에게 주는 것입니다. 하지만 위험합니다. 택배 기사가 카드를 가지고 있다면 나중에도 언제든 아파트에 들어올 수 있기 때문입니다.
더 현명한 방법은 경비실에 가서 ‘택배 기사가 들어와야 합니다’라고 말하는 것입니다. 경비원은 택배 기사에게 임시 출입증을 발급합니다. 출입증에는 ‘오늘 오후 2시부터 4시까지만 입장 가능하며 A동에만 갈 수 있음’이라고 적혀 있습니다. 배송이 끝나면 출입증은 무효가 됩니다.
이것이 OAuth의 핵심 개념입니다.
이 예에서 각 역할은 다음과 같습니다.
- 나 = 사용자(로그인하려는 사람)
- 택배 기사 = 서드파티 애플리케이션(예: 내가 개발한 웹사이트)
- 아파트 경비원 = OAuth 제공자(Google, WeChat, GitHub 등)
- 출입 카드 = 내 비밀번호(다른 사람에게 주면 안 됨)
- 임시 출입증 = access_token(유효 기간과 권한 제한이 있음)
비밀번호를 서드파티 앱에 넘길 필요 없이, OAuth 제공자에게 ‘임시 출입증’을 받도록 승인하기만 하면 됩니다.
OAuth의 5단계 흐름(인증 코드 방식)
이제 이 과정을 Next.js OAuth 로그인에 구체적으로 적용해 보겠습니다.
1단계: 사용자가 웹사이트의 ‘Google로 로그인’ 버튼을 클릭합니다.
2단계: 웹사이트가 사용자를 Google 승인 페이지로 리디렉션합니다. URL은 대략 다음과 같습니다.
https://accounts.google.com/o/oauth2/auth?
client_id=내_애플리케이션_ID
&redirect_uri=http://localhost:3000/api/auth/callback/google
&response_type=code
&scope=openid email profile
&state=무작위_문자열
이 단계에서 웹사이트는 Google에 이렇게 말합니다. ‘저는 특정 애플리케이션(client_id)이고, 사용자가 Google 계정으로 로그인하려고 합니다. 사용자에게 승인을 확인받은 뒤 이 주소(redirect_uri)로 돌려보내 주세요.’
3단계: 사용자는 Google 페이지에서 ‘이 애플리케이션이 기본 정보에 접근하려고 합니다’라는 안내를 보고 ‘허용’을 클릭합니다.
4단계: Google은 사용자를 웹사이트의 redirect_uri로 돌려보내면서 URL에 **인증 코드(code)**를 포함합니다.
http://localhost:3000/api/auth/callback/google?code=ABCD1234&state=무작위_문자열
이 code는 최종 출입증이 아니라 증명서에 가깝습니다. 유효 기간이 매우 짧고(보통 10분) 한 번만 사용할 수 있습니다.
5단계: 웹사이트 백엔드는 이 code와 애플리케이션의 비밀번호인 client_secret을 Google에 보내 실제 access_token으로 교환합니다.
// 백엔드 코드(간소화한 예시)
const response = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
body: JSON.stringify({
code: 'ABCD1234',
client_id: '내_애플리케이션_ID',
client_secret: '내_애플리케이션_비밀번호',
redirect_uri: 'http://localhost:3000/api/auth/callback/google',
grant_type: 'authorization_code',
}),
})
const { access_token } = await response.json()
access_token을 받으면 웹사이트는 이를 사용해 Google에서 사용자의 이메일, 프로필 사진, 이름 등의 정보를 가져올 수 있습니다.
6단계(선택 사항): access_token으로 사용자 정보를 가져옵니다.
const userInfo = await fetch('https://www.googleapis.com/oauth2/v2/userinfo', {
headers: {
Authorization: `Bearer ${access_token}`,
},
})
모든 과정이 끝나면 웹사이트는 ‘이 사용자가 누구인지’ 알 수 있고, session을 만들어 로그인 상태로 전환할 수 있습니다.
핵심 개념을 쉽게 풀어보기
여기까지 읽어도 몇몇 용어가 여전히 헷갈릴 수 있습니다. 다시 쉽게 설명해 보겠습니다.
-
client_id: Google에 등록된 애플리케이션의 ‘신분증 번호’입니다. 공개되어도 문제가 없습니다.
-
client_secret: 애플리케이션의 ‘비밀번호’입니다. 절대로 유출해서는 안 되며, 서버에서만 사용해야 합니다. 다른 사람이 client_secret을 얻으면 애플리케이션을 사칭해 사용자 정보를 가져갈 수 있습니다.
-
redirect_uri: 승인 후 Google이 사용자를 돌려보낼 주소입니다. 이 주소는 Google Cloud Console에 미리 등록해야 하며, Google은 매우 엄격하게 비교합니다. 슬래시 하나만 달라도 오류가 발생합니다. 바로 골치 아픈 redirect_uri_mismatch 오류입니다.
-
state: CSRF 공격을 막기 위한 무작위 문자열입니다. 승인 요청을 시작할 때 state를 만들면 Google이 같은 값을 그대로 돌려줍니다. 반환된 state가 보낸 값과 같은지 확인해야 하며, 다르다면 위조된 요청일 수 있습니다.
-
code: 임시 인증 코드입니다. 유효 기간이 약 10분으로 짧고 한 번만 사용할 수 있습니다. 사용자가 Google에서 승인을 완료했다는 사실을 증명합니다.
-
access_token: 실제 ‘출입증’입니다. 웹사이트는 이를 사용해 사용자를 대신하여 Google에서 정보를 가져올 수 있습니다. access_token에도 유효 기간이 있으며, 보통 1시간에서 며칠까지 다양합니다.
왜 code와 access_token을 두 단계로 나눌까요?
access_token을 바로 반환하면 되는데 굳이 code를 다시 교환하는 이유가 궁금할 수 있습니다.
핵심은 보안입니다. Code는 브라우저 리디렉션을 통해 전달되므로 프론트엔드에서 볼 수 있지만, access_token은 백엔드 서버 사이에서 전달되므로 프론트엔드에 노출되지 않습니다. access_token을 URL로 직접 반환하면 브라우저 기록, 로그, 네트워크 모니터링을 통해 유출될 수 있습니다. code를 token으로 교환하려면 백엔드에만 있는 client_secret이 필요하므로 훨씬 안전합니다.
Next.js + NextAuth.js로 Google 로그인 설정하기(가장 쉬운 입문 방법)
왜 NextAuth.js를 선택하나요?
OAuth 흐름을 직접 구현하는 일은 꽤 번거롭습니다. 콜백 처리, session 관리, CSRF 공격 방어, token 저장 등 신경 써야 할 세부 사항이 많습니다.
다행히 NextAuth.js(현재 명칭은 Auth.js v5)라는 라이브러리가 이 작업을 대신해 줍니다. GitHub에서 15k개가 넘는 stars를 받았고 활발한 커뮤니티를 갖추고 있으며, Google, GitHub, WeChat, Twitter 등 50개 이상의 OAuth 제공자를 지원합니다. 최신 버전은 Next.js 14+ App Router를 지원하며 이전보다 설정도 훨씬 간단합니다.
쉽게 말하면 NextAuth.js를 사용해 반복 작업의 80%를 줄일 수 있습니다.
1단계: Google Cloud에서 애플리케이션 만들기
코드를 작성하기 전에 Google에 애플리케이션을 ‘등록’하고 client_id와 client_secret을 받아야 합니다.
-
Google Cloud Console을 열고 Google 계정으로 로그인합니다.
-
처음 사용한다면 프로젝트(Project)를 만듭니다. 프로젝트 이름은 ‘My Next.js App’처럼 자유롭게 정하면 됩니다.
-
왼쪽 메뉴에서 ‘API 및 서비스’ → ‘사용자 인증 정보’(Credentials)로 이동합니다.
-
‘사용자 인증 정보 만들기’를 클릭하고 ‘OAuth 클라이언트 ID’를 선택합니다.
-
처음 사용하는 경우 ‘OAuth 동의 화면’을 먼저 설정하라는 메시지가 나올 수 있습니다. 애플리케이션 이름과 지원 이메일을 입력하고 나머지는 우선 건너뜁니다. 사용자 유형은 ‘외부’를 선택하면 테스트 단계에서는 검토가 필요하지 않습니다.
-
OAuth 클라이언트 ID 생성 화면으로 돌아가 애플리케이션 유형을 ‘웹 애플리케이션’으로 선택합니다.
-
중요한 단계입니다. ‘승인된 리디렉션 URI’를 설정합니다. 다음 두 주소를 입력합니다.
- 로컬 개발:
http://localhost:3000/api/auth/callback/google - 프로덕션 환경(배포 후 추가):
https://yourdomain.com/api/auth/callback/google
이 주소는 코드의 주소와 완전히 같아야 합니다. 슬래시가 하나 많거나 적어도 redirect_uri_mismatch 오류가 발생합니다. 저도 처음에는 여기서 막혔습니다.
- 로컬 개발:
-
‘만들기’를 클릭하면 Client ID와 Client Secret이 표시되는 대화 상자가 열립니다. 두 값을 복사해 두세요. 잠시 후 사용합니다.
2단계: NextAuth.js 설치 및 환경 변수 설정
Next.js 프로젝트에서 먼저 패키지를 설치합니다.
npm install next-auth@beta
v5 최신 버전인 @beta 버전을 설치해야 합니다.
그런 다음 프로젝트 루트에 .env.local 파일을 만들고 앞에서 받은 Client ID와 Secret을 입력합니다.
GOOGLE_CLIENT_ID=내_Client_ID.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=내_Client_Secret
NEXTAUTH_SECRET=임의로_생성한_무작위_문자열
NEXTAUTH_URL=http://localhost:3000
다음 명령으로 NEXTAUTH_SECRET을 생성할 수 있습니다.
openssl rand -base64 32
프로덕션에 배포할 때는 NEXTAUTH_URL을 실제 도메인으로 변경해야 합니다.
3단계: NextAuth 설정 파일 만들기
app/api/auth/[...nextauth]/route.ts 파일을 만듭니다. Pages Router를 사용한다면 경로는 pages/api/auth/[...nextauth].ts입니다.
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
],
callbacks: {
async signIn({ user, account, profile }) {
// 로그인 성공 콜백이며, 여기에서 사용자 정보를 데이터베이스에 저장할 수 있습니다.
console.log("사용자 로그인:", user)
return true // true를 반환하면 로그인을 허용합니다.
},
async session({ session, token }) {
// session 내용을 사용자 정의합니다.
if (session.user) {
session.user.id = token.sub // session에 user id를 추가합니다.
}
return session
},
},
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
이것으로 끝입니다. NextAuth.js가 모든 OAuth 흐름을 자동으로 처리합니다. /api/auth/callback/google 라우트도 자동으로 생성되므로 별도로 코드를 작성할 필요가 없습니다.
4단계: 로그인 버튼 만들기
어떤 컴포넌트에서든 다음처럼 로그인 버튼을 작성할 수 있습니다.
'use client' // App Router에서는 클라이언트 컴포넌트 표시가 필요합니다.
import { signIn, signOut, useSession } from "next-auth/react"
export default function LoginButton() {
const { data: session } = useSession()
if (session) {
// 사용자가 로그인한 상태입니다.
return (
<div>
<p>환영합니다, {session.user?.name}</p>
<img src={session.user?.image || ''} alt="프로필 사진" />
<button onClick={() => signOut()}>로그아웃</button>
</div>
)
}
// 사용자가 로그인하지 않은 상태입니다.
return <button onClick={() => signIn('google')}>Google로 로그인</button>
}
signIn('google')은 자동으로 Google 승인 페이지로 이동합니다. 승인이 끝난 뒤 돌아오면 사용자는 로그인된 상태가 됩니다. 아주 간단합니다.
5단계: 루트 레이아웃을 SessionProvider로 감싸기
모든 컴포넌트에서 useSession을 사용하려면 루트 레이아웃을 Provider로 감싸야 합니다.
// app/layout.tsx
import { SessionProvider } from "next-auth/react"
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<SessionProvider>{children}</SessionProvider>
</body>
</html>
)
}
완료되었습니다. 이제 Google로 로그인할 수 있는 Next.js 애플리케이션이 생겼습니다.
자주 발생하는 문제 해결
문제 1: redirect_uri_mismatch
가장 흔한 오류입니다. 저도 처음 설정할 때 이 문제를 겪었습니다.
오류 메시지는 대략 Error 400: redirect_uri_mismatch와 같습니다.
원인: Google Cloud Console에 등록한 콜백 주소와 실제 요청 주소가 일치하지 않습니다.
해결 방법:
- Google Cloud Console의 ‘승인된 리디렉션 URI’가
http://localhost:3000/api/auth/callback/google인지 확인합니다. 불필요한 슬래시도 확인하세요. .env.local의NEXTAUTH_URL이http://localhost:3000인지 확인합니다.- 포트를 3001처럼 변경했다면 양쪽 설정을 모두 변경해야 합니다.
문제 2: 로컬에서는 정상인데 배포 후 로그인이 실패함
저도 이 문제를 겪었습니다. 로컬 테스트에서는 모든 로그인이 정상이었지만 Vercel에 배포한 뒤 로그인 버튼을 클릭해도 반응이 없거나 오류가 발생했습니다.
원인: 프로덕션 환경 변수를 업데이트하지 않았습니다.
해결 방법:
- Google Cloud Console의 ‘승인된 리디렉션 URI’에 프로덕션 도메인
https://yourdomain.com/api/auth/callback/google을 추가합니다. - Vercel 또는 사용 중인 호스팅 플랫폼의 환경 변수 설정에서
NEXTAUTH_URL을https://yourdomain.com으로 변경합니다. - 다시 배포합니다.
문제 3: 로그인 후 session이 null임
useSession()이 반환하는 session이 계속 null이라면 루트 레이아웃을 <SessionProvider>로 감싸는 것을 잊지 않았는지 확인하세요.
문제 4: TypeError: Cannot read property ‘user’ of null
대개 session을 불러오는 중인데 session.user에 접근하려 해서 발생합니다.
해결 방법: 먼저 session이 있는지 확인합니다.
const { data: session, status } = useSession()
if (status === 'loading') {
return <div>불러오는 중...</div>
}
if (!session) {
return <div>로그인하지 않음</div>
}
// 이 지점부터 session.user에 안전하게 접근할 수 있습니다.
GitHub 로그인 설정(차이점 이해하기)
Google 로그인을 설정해 봤다면 GitHub 로그인은 훨씬 간단합니다. 다만 GitHub와 Google 사이에는 알아 둘 만한 차이점이 있습니다.
GitHub OAuth와 Google OAuth의 차이
공통점: 둘 다 표준 OAuth 2.0 인증 코드 방식을 사용하므로 전체 흐름은 같습니다.
차이점:
- 더 세분화된 권한 관리: GitHub의 scope(권한 범위)는 Google보다 복잡합니다. 기본적으로 사용자의 공개 정보만 가져올 수 있으며, 특히 비공개 이메일을 가져오려면
user:email권한을 별도로 요청해야 합니다. - 더 유연한 콜백 주소 설정: Google은 완전한 콜백 URL을 요구하지만 GitHub는 도메인만 입력해도 됩니다.
- 애플리케이션 유형: GitHub는 개인 계정과 조직 계정의 OAuth App을 모두 지원합니다.
1단계: GitHub에서 OAuth App 만들기
-
GitHub에 로그인하고 오른쪽 위 프로필 사진 → Settings → 왼쪽 메뉴의 ‘Developer settings’로 이동합니다.
-
‘OAuth Apps’ → ‘New OAuth App’을 클릭합니다.
-
애플리케이션 정보를 입력합니다.
- Application name: 애플리케이션 이름(사용자가 승인할 때 표시됨)
- Homepage URL: 웹사이트 홈 주소(예:
http://localhost:3000) - Authorization callback URL:
http://localhost:3000/api/auth/callback/github
-
‘Register application’을 클릭한 다음 ‘Generate a new client secret’을 클릭하고 Client ID와 Client Secret을 복사합니다.
2단계: 환경 변수 설정
.env.local에 GitHub 설정을 추가합니다.
GITHUB_CLIENT_ID=내_GitHub_Client_ID
GITHUB_CLIENT_SECRET=내_GitHub_Client_Secret
3단계: NextAuth 설정 업데이트
앞에서 만든 route.ts 파일에 GitHubProvider를 추가합니다.
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"
import GitHubProvider from "next-auth/providers/github"
export const authOptions = {
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
// 사용자의 비공개 이메일이 필요하다면 이 설정을 추가합니다.
authorization: {
params: {
scope: 'read:user user:email'
}
}
}),
],
callbacks: {
// ... 앞에서 작성한 callbacks
},
}
const handler = NextAuth(authOptions)
export { handler as GET, handler as POST }
4단계: 로그인 버튼 업데이트
로그인 버튼 컴포넌트에 GitHub 로그인 옵션을 추가합니다.
return (
<div>
<button onClick={() => signIn('google')}>Google로 로그인</button>
<button onClick={() => signIn('github')}>GitHub로 로그인</button>
</div>
)
이것으로 설정이 끝났습니다.
scope(권한 범위) 설명
GitHub의 scope는 애플리케이션이 가져올 수 있는 사용자 정보를 제어합니다. 자주 사용하는 scope는 다음과 같습니다.
read:user: 이름, 프로필 사진, bio 등 사용자의 공개 및 비공개 정보를 읽습니다.user:email: 비공개 이메일을 포함한 사용자의 이메일을 읽습니다.public_repo: 사용자의 공개 저장소에 접근합니다.repo: 사용자의 모든 공개 및 비공개 저장소에 접근합니다. 권한이 매우 크므로 신중하게 사용해야 합니다.
로그인 용도라면 read:user user:email이면 충분합니다.
user:email scope를 추가하지 않으면 NextAuth.js는 사용자의 공개 이메일만 가져올 수 있습니다. 사용자가 GitHub 설정에서 이메일을 숨겼다면 session.user.email은 null이 됩니다. 저도 이 때문에 한동안 코드가 잘못된 줄 알고 헤맸습니다.
주의할 점: GitHub 사용자는 공개 이메일이 없을 수 있습니다
이메일을 반드시 요구하는 Google과 달리 GitHub 사용자는 이메일을 공개하지 않을 수 있습니다. 애플리케이션에 사용자 이메일이 꼭 필요하다면, 예를 들어 알림을 보내야 한다면 signIn callback에서 확인해야 합니다.
async signIn({ user, account }) {
if (account?.provider === 'github' && !user.email) {
// 사용자의 공개 이메일이 없으면 로그인을 거부하거나 안내할 수 있습니다.
console.log("GitHub 사용자가 이메일을 제공하지 않았습니다.")
return false // 로그인 거부
}
return true
}
WeChat 로그인 설정(중국 환경의 특수성)
이제 WeChat 로그인을 살펴보겠습니다. 솔직히 WeChat 로그인은 Google이나 GitHub보다 훨씬 복잡합니다. 생태계가 해외 서비스와 다르기 때문입니다.
WeChat 로그인이 특별한 이유
몇 가지 핵심 차이점이 있습니다.
- QR 코드 스캔 필요: PC 웹사이트의 WeChat 로그인에서는 사용자가 휴대전화로 QR 코드를 스캔해 승인해야 합니다. Google이나 GitHub처럼 웹페이지에서 클릭만 해서는 완료되지 않습니다.
- 공식 NextAuth.js Provider 없음: NextAuth.js에는 WeChat provider가 내장되어 있지 않아 직접 작성해야 합니다.
- 콜백 주소 제한이 많음: WeChat은 콜백 도메인에 ICP 등록을 요구하고 localhost를 허용하지 않아 개발과 디버깅이 까다롭습니다.
- openid와 unionid가 있음: WeChat의 사용자 식별자는 조금 특수합니다. 같은 사용자라도 애플리케이션마다 openid가 다르며, 여러 애플리케이션에서 사용자 데이터를 공유하려면 unionid를 사용해야 합니다.
1단계: WeChat Open Platform 등록
-
WeChat Open Platform을 열고 계정을 등록합니다.
-
‘웹사이트 애플리케이션’을 만듭니다. 공식 계정이나 미니 프로그램과는 다르므로 주의하세요.
-
웹사이트 정보를 입력하고 웹사이트 스크린샷을 업로드한 뒤 심사를 기다립니다. 심사에는 일반적으로 영업일 기준 1~3일이 걸립니다.
-
승인이 완료되면 client_id와 client_secret에 해당하는 AppID와 AppSecret을 받습니다.
-
‘개발 정보’에서 승인 콜백 도메인을 설정합니다.
yourdomain.com처럼 전체 경로가 아닌 도메인만 입력합니다.
2단계: 커스텀 WeChat Provider 작성
NextAuth.js에는 WeChat provider가 내장되어 있지 않으므로 직접 작성해야 합니다. 프로젝트에 lib/wechat-provider.ts 파일을 만듭니다.
import type { OAuthConfig, OAuthUserConfig } from "next-auth/providers"
export interface WeChatProfile {
openid: string
nickname: string
headimgurl: string
sex: number
province: string
city: string
country: string
unionid?: string
}
export default function WeChatProvider<P extends WeChatProfile>(
options: OAuthUserConfig<P>
): OAuthConfig<P> {
return {
id: "wechat",
name: "WeChat",
type: "oauth",
// WeChat 승인 주소이며 PC 웹사이트 애플리케이션은 이 주소를 사용합니다.
authorization: {
url: "https://open.weixin.qq.com/connect/qrconnect",
params: {
scope: "snsapi_login",
appid: options.clientId,
response_type: "code",
},
},
// 인증 코드를 access_token으로 교환하는 주소입니다.
token: {
url: "https://api.weixin.qq.com/sns/oauth2/access_token",
params: {
appid: options.clientId,
secret: options.clientSecret,
grant_type: "authorization_code",
},
},
// 사용자 정보를 가져오는 주소입니다.
userinfo: {
url: "https://api.weixin.qq.com/sns/userinfo",
async request({ tokens, provider }) {
const res = await fetch(
`${provider.userinfo?.url}?access_token=${tokens.access_token}&openid=${tokens.openid}&lang=zh_CN`
)
return await res.json()
},
},
// WeChat이 반환한 사용자 정보를 NextAuth 표준 형식으로 변환합니다.
profile(profile) {
return {
id: profile.openid,
name: profile.nickname,
email: null, // WeChat은 이메일을 제공하지 않습니다.
image: profile.headimgurl,
}
},
options,
}
}
3단계: 환경 변수 설정
.env.local에 WeChat 설정을 추가합니다.
WECHAT_CLIENT_ID=내_WeChat_AppID
WECHAT_CLIENT_SECRET=내_WeChat_AppSecret
4단계: NextAuth에서 사용하기
route.ts를 업데이트합니다.
import WeChatProvider from "@/lib/wechat-provider"
export const authOptions = {
providers: [
GoogleProvider({...}),
GitHubProvider({...}),
WeChatProvider({
clientId: process.env.WECHAT_CLIENT_ID!,
clientSecret: process.env.WECHAT_CLIENT_SECRET!,
}),
],
}
5단계: 로컬 개발에서 테스트하는 방법
이 부분이 가장 불편합니다. WeChat은 localhost를 콜백 도메인으로 허용하지 않으므로 로컬 개발 환경에서 직접 테스트할 수 없습니다.
두 가지 해결 방법이 있습니다.
방법 1: 터널링 도구 사용
ngrok이나 cpolar을 사용해 로컬 서비스를 인터넷에 공개합니다.
# ngrok 설치
brew install ngrok
# 터널 시작
ngrok http 3000
ngrok은 https://abc123.ngrok.io 같은 임시 도메인을 제공합니다. 이 도메인을 WeChat Open Platform의 승인 콜백 도메인에 입력하고 .env.local의 NEXTAUTH_URL을 업데이트합니다.
NEXTAUTH_URL=https://abc123.ngrok.io
방법 2: hosts 파일 설정
로컬 hosts 파일(Mac/Linux는 /etc/hosts, Windows는 C:\Windows\System32\drivers\etc\hosts)에 다음 한 줄을 추가합니다.
127.0.0.1 dev.yourdomain.com
그런 다음 http://dev.yourdomain.com:3000으로 접속하고 WeChat Open Platform의 콜백 도메인을 dev.yourdomain.com으로 설정합니다.
하지만 이 방법에는 문제가 있습니다. WeChat은 콜백 도메인에 ICP 등록을 요구하므로 dev.yourdomain.com 역시 통과하지 못합니다. 가장 확실한 방법은 방법 1입니다.
WeChat 로그인에서 별도로 처리할 점
WeChat 사용자는 이메일이 없습니다. 애플리케이션이 이메일에 강하게 의존한다면 별도 처리가 필요합니다.
async signIn({ user, account }) {
if (account?.provider === 'wechat') {
// WeChat 사용자는 이메일이 없으므로 직접 입력하게 할 수 있습니다.
// 또는 openid를 고유 식별자로 사용해 데이터베이스에 저장합니다.
console.log("WeChat 사용자 openid:", user.id)
}
return true
}
보안 모범 사례(흔한 함정 피하기)
OAuth 로그인을 설정한 뒤에도 주의해야 할 보안 세부 사항이 있습니다. 모두 제가 직접 겪으며 배운 내용입니다.
1. client_secret은 절대로 유출하지 마세요
잘못된 방법:
// ❌ 절대 이렇게 작성하지 마세요!
const clientSecret = "abc123def456" // 코드에 하드코딩
client_secret을 프론트엔드 코드에 작성하거나 Git 저장소에 커밋하면, 다른 사람이 이를 이용해 애플리케이션을 사칭하고 사용자 정보를 가져갈 수 있습니다.
올바른 방법:
- 환경 변수에 저장하고
.env.local을.gitignore에 추가합니다. - client_secret은 서버에서만 사용합니다. NextAuth.js의 API route는 백엔드이므로 문제없습니다.
- 배포 시 플랫폼의 환경 변수 관리 기능을 사용합니다. Vercel에서는 Settings → Environment Variables를 사용합니다.
2. state 매개변수의 역할(CSRF 공격 방어)
OAuth 흐름의 state 매개변수는 CSRF 공격을 막는 역할을 합니다.
공격 상황을 생각해 봅시다. 공격자가 위조한 인증 코드가 포함된 악성 링크를 만들고 사용자가 클릭하도록 유도합니다. 애플리케이션이 state를 검증하지 않으면 이 가짜 인증 코드를 token으로 교환하려다 공격에 노출될 수 있습니다.
다행히 NextAuth.js가 state 검증을 자동으로 처리하므로 별도의 코드를 작성할 필요가 없습니다.
NextAuth.js 없이 OAuth를 직접 구현한다면 다음을 기억하세요.
- 승인 요청을 시작할 때 무작위 state를 생성해 session이나 cookie에 저장합니다.
- 콜백에서 반환된 state가 보낸 값과 일치하는지 확인합니다.
- 일치하지 않으면 요청을 거부합니다.
3. 콜백 주소 허용 목록
OAuth 제공자(Google, GitHub, WeChat) 콘솔에 사용할 수 있는 모든 콜백 주소를 등록합니다.
- 개발 환경:
http://localhost:3000/api/auth/callback/[provider] - 프리뷰 환경:
https://preview.yourdomain.com/api/auth/callback/[provider] - 프로덕션 환경:
https://yourdomain.com/api/auth/callback/[provider]
편리하더라도 https://*.yourdomain.com 같은 와일드카드는 사용하지 마세요. 공격자가 악용하지 못하도록 모든 도메인을 명시적으로 등록해야 합니다.
4. Token 저장 보안
NextAuth.js는 기본적으로 JWT 방식으로 session을 저장하며 token은 HttpOnly Cookie에 보관합니다. 좋은 설계입니다.
- HttpOnly: 프론트엔드 JavaScript에서 이 cookie를 읽을 수 없으므로 XSS 공격에 의한 token 탈취를 막습니다.
- Secure(프로덕션 환경): HTTPS로만 전송되어 중간자 공격을 방지합니다.
해야 할 일은 다음과 같습니다.
- access_token을 프론트엔드에 반환하지 마세요. NextAuth.js는 기본적으로 반환하지 않으며 직접 추가해서도 안 됩니다.
- 사용자 정보를 영구 저장해야 한다면
signIncallback에서 데이터베이스에 저장하고, session에는 user id와 email 등 필요한 정보만 보관합니다.
5. 인증 코드의 유효 기간
OAuth 인증 코드(code)의 유효 기간은 약 10분으로 매우 짧고 한 번만 사용할 수 있습니다.
이는 보안을 위한 설계입니다. 공격자가 code를 가로채더라도 사용하려는 시점에는 이미 만료되었거나 애플리케이션이 사용했을 가능성이 큽니다.
사용자가 승인 페이지에 너무 오래 머물렀다면, 예를 들어 커피를 마시고 돌아왔다면 code가 만료될 수 있습니다. NextAuth.js는 이 상황을 자동으로 처리해 승인 흐름을 다시 시작합니다.
6. 프로덕션 환경 체크리스트
배포 전에 다음 설정을 확인하세요.
- NEXTAUTH_URL, NEXTAUTH_SECRET, 각 provider의 client_id와 client_secret 등 환경 변수를 모두 설정했나요?
- NEXTAUTH_URL을 localhost가 아닌 프로덕션 도메인으로 변경했나요?
- OAuth 제공자 콘솔에 프로덕션 환경 콜백 주소를 등록했나요?
- secret이 Git에 커밋되지 않도록
.env.local이.gitignore에 포함되어 있나요? - 프로덕션 NEXTAUTH_SECRET을 무작위로 생성했나요? 개발 환경의 값을 그대로 사용하지 마세요.
마무리
지금까지의 내용을 빠르게 정리해 보겠습니다.
OAuth의 본질은 ‘임시 출입증’입니다. 비밀번호를 서드파티 앱에 넘기는 대신, 유효 기간과 권한 제한이 있는 출입증(access_token)을 OAuth 제공자에게서 받도록 승인합니다. 전체 흐름은 두 단계입니다. 먼저 인증 코드(code)로 사용자의 승인을 증명하고, code와 client_secret을 실제 token으로 교환합니다.
Next.js에서 소셜 로그인을 설정할 때는 Google이 가장 간단해 입문용으로 좋습니다. GitHub는 조금 더 복잡하며 scope 설정과 사용자의 공개 이메일이 없을 수 있다는 점에 주의해야 합니다. WeChat은 가장 특수하며 QR 코드 스캔, 커스텀 provider, 콜백 도메인의 ICP 등록이 필요하고 로컬 디버깅도 비교적 어렵습니다.
보안에서는 세 가지 원칙을 기억하세요.
- client_secret은 서버에서만 사용하고 절대로 유출하지 않습니다.
- state 매개변수를 반드시 검증합니다. NextAuth.js가 자동으로 처리합니다.
- 콜백 주소는 허용 목록을 사용하고 와일드카드를 쓰지 않습니다.
OAuth를 처음 설정한다면 Google 로그인부터 시작해 이 글의 코드를 한 단계씩 따라 해 보세요. 성공적으로 설정했을 때 느끼는 성취감은 꽤 큽니다.
문제가 생겨도 당황하지 마세요. 오류의 90%는 redirect_uri 설정이 잘못되었거나 환경 변수가 빠져서 발생합니다. 설정을 한 번 점검하면 대부분 해결할 수 있습니다.
마지막으로 NextAuth.js 공식 문서도 읽어 보시길 권합니다. 데이터베이스에 session 저장하기, 사용자 정의 로그인 페이지, JWT 설정 등 더 많은 고급 사용법이 있습니다. OAuth 2.0 표준 명세(RFC 6749)도 읽을 가치가 있습니다. 원리를 이해하고 나면 새로운 문제가 생겨도 응용해서 해결할 수 있습니다.
Next.js OAuth 소셜 로그인 전체 설정 절차
Google, GitHub, WeChat 소셜 로그인을 처음부터 설정합니다.
⏱️ Estimated time: 2 hr
- 1
Step 1: NextAuth.js 설치 및 초기화
의존성 설치:
• npm install next-auth
• app/api/auth/[...nextauth]/route.ts 생성
기본 설정:
• NEXTAUTH_URL 설정(로컬: http://localhost:3000, 프로덕션: 실제 도메인)
• NEXTAUTH_SECRET 설정(무작위 문자열 생성)
• 기본 providers 배열 구성 - 2
Step 2: Google 로그인 설정
단계:
1. Google Cloud Console 접속
2. OAuth 클라이언트 ID 생성
3. 승인된 리디렉션 URI 설정: http://localhost:3000/api/auth/callback/google
4. Client ID와 Client Secret 확인
5. 환경 변수 GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET에 추가
6. NextAuth 설정에 GoogleProvider 추가
주의: 프로덕션 콜백 주소는 설정값과 완전히 일치해야 합니다. - 3
Step 3: GitHub 로그인 설정
단계:
1. GitHub Settings > Developer settings > OAuth Apps 접속
2. 새 OAuth App 생성
3. Authorization callback URL 설정: http://localhost:3000/api/auth/callback/github
4. Client ID와 Client Secret 확인
5. 환경 변수 GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET에 추가
6. NextAuth 설정에 GitHubProvider 추가
주의: 이메일을 가져오려면 scope에 user:email이 포함되어야 합니다. - 4
Step 4: WeChat 로그인 설정(선택 사항)
단계:
1. WeChat Open Platform 계정 등록(기업 인증 필요)
2. 웹사이트 앱을 만들고 AppID와 AppSecret 발급
3. 승인 콜백 도메인 설정(ICP 등록 필요)
4. 커스텀 Provider 작성(NextAuth에 WeChat이 내장되어 있지 않음)
5. QR 코드 로그인 흐름 구현
주의: WeChat 로그인은 비교적 복잡하므로 Google과 GitHub 설정을 먼저 마친 뒤 시도하는 것이 좋습니다. - 5
Step 5: 로그인 페이지와 버튼 만들기
로그인 컴포넌트 생성:
• signIn('google')로 로그인 시작
• signOut()으로 로그아웃
• useSession()으로 사용자 정보 가져오기
• SessionProvider로 앱 감싸기
예시:
<button onClick={() => signIn('google')}>
Google로 로그인
</button> - 6
Step 6: 테스트 및 디버깅
테스트 항목:
• 로컬 테스트: 콜백 주소가 http://localhost:3000인지 확인
• 프로덕션: 콜백 주소가 실제 도메인과 일치하는지 확인
• 환경 변수가 올바르게 설정되었는지 확인
• 브라우저 콘솔과 서버 로그 확인
자주 발생하는 오류:
• redirect_uri_mismatch: 콜백 주소 불일치
• invalid_client: Client ID 또는 Secret 오류
• access_denied: 사용자가 승인을 거부함
FAQ
OAuth 2.0은 어떻게 작동하나요?
redirect_uri_mismatch 오류는 어떻게 해결하나요?
해결 방법:
1) OAuth 제공자 콘솔에 등록한 콜백 주소와 코드의 주소가 프로토콜, 도메인, 포트, 경로까지 완전히 같은지 확인합니다.
2) 로컬 개발에서는 http://localhost:3000, 프로덕션에서는 실제 도메인을 사용합니다.
3) 불필요한 슬래시나 매개변수가 없는지 확인합니다.
NextAuth.js를 사용하는 것과 OAuth를 직접 구현하는 것은 무엇이 다른가요?
• 50개 이상의 제공자 지원
• 승인 흐름, session 관리, CSRF 방어 등을 자동 처리
직접 구현할 경우 다음 작업을 직접 처리해야 합니다.
• 인증 코드 교환
• token 저장
• state 검증 등
코드가 많고 오류가 발생하기 쉬우므로 NextAuth.js 사용을 권장합니다.
사용자의 이메일 주소는 어떻게 가져오나요?
• Google: 기본적으로 이메일 반환
• GitHub: scope에 user:email을 포함해야 하며 사용자가 이메일을 공개로 설정해야 함
• WeChat: unionid로 조회해야 함
가져오지 못한 경우 로그인 후 사용자가 직접 입력하도록 할 수 있습니다.
로컬 테스트에서는 정상인데 프로덕션에 배포하면 오류가 발생하는 이유는 무엇인가요?
확인 항목:
1) 프로덕션 NEXTAUTH_URL이 올바른지
2) OAuth 제공자 콘솔의 콜백 주소에 프로덕션 도메인이 포함되었는지
3) 환경 변수가 올바르게 설정되었는지
4) 방화벽이나 프록시의 영향이 있는지
여러 로그인 방식을 동시에 지원할 수 있나요?
OAuth 로그인은 안전한가요?
다만 다음 사항에 주의해야 합니다.
1) client_secret은 반드시 비밀로 유지하고 서버에서만 사용합니다.
2) state 매개변수로 CSRF 공격을 방지합니다(NextAuth.js가 자동 처리).
3) 콜백 주소는 허용 목록을 사용하고 와일드카드를 쓰지 않습니다.
4) 의존성 패키지를 정기적으로 업데이트합니다.
5분 읽기 · 게시일: 2025년 12월 19일 · 수정일: 2026년 9월 4일
Next.js 완전 가이드
검색으로 들어왔다면 같은 시리즈의 이전 글이나 다음 글로 이동하는 것이 가장 빠릅니다.
이전
Next.js 라우트 보호와 권한 제어: Middleware와 다층 방어 완벽 가이드
Next.js 라우트 보호와 권한 제어를 Middleware부터 다층 방어 아키텍처까지 자세히 살펴보고, NextAuth와 getServerSession으로 안전한 RBAC 시스템을 구현하는 전체 코드 예제를 소개합니다.
45편 중 10편
다음
Next.js OAuth 로그인 실전: Google, GitHub, WeChat 소셜 로그인 연동 가이드
OAuth 원리부터 실전 설정까지, 택배 대리 수령 비유로 인증 흐름을 이해하고 NextAuth.js로 Google, GitHub, WeChat 로그인을 구현하는 방법과 전체 오류 해결 과정을 설명합니다.
45편 중 12편



댓글
GitHub로 로그인하여 댓글을 남기세요