어떤 서비스를 제작하든 가장 먼저 구현하게 되는 기능이 있습니다.

처음 소셜로그인 기능을 개발하는 분들도 글만 쉽게 따라할 수 있도록 구현 방법, 제가 마주한 (아주 사소한) 오류들과 해결 방법을 기록해보았습니다.
글의 마지막에 한장 요약 사진도 올려두었으니 이전에 구현해 본 경험이 있다면 그 이미지만 참고하셔도 충분히 구현하실 수 있을 거예요!
우선 전체적인 프로세스는 다음과 같습니다.
- 설치
- api 루트 추가하기
sessionProvider추가하기- 홈페이지에서 로그인 api 사용 등록하기
- 환경변수 등록하기
- providers 옵션 추가하기
- 어댑터 세팅하여 외부 DB에 저장하기 (prisma)
- signIn() 함수 호출하기, useSession 사용하기
npm install next-auth
yarn add next-auth
pnpm add next-auth
app/api/auth/[…nextauth]/route.ts 경로에 아래 코드를 추가해줍니다.
import NextAuth from "next-auth"
const handler = NextAuth({
//여기에 옵션 추가!
})
export { handler as GET, handler as POST }
💡개발 초보라면 이런 실수도 가능하다!
개발 초보자의 경우, 여러가지 강의 영상이나 블로그 글을 찾아보면서 따라하시는 분들이 많으실텐데요. Next.js의 버전에 따라 export 하는 방식이 다릅니다.
공식 문서에서는 위와 같이 App Router를 사용하는 Next.js 13.2 이상 버전에서는 Route Handler 를 사용하라고 나와있어요.
공식문서 확인해보기 | NextAuth.jspages 경로를 사용하는 공식 문서 예제에서 마지막 줄이 다르니 꼭 확인하세요.
그리고 여기서는 파일도 동적 라우팅 기능으로 […nextauth].js 를 생성했는데,
Next.js 13 이후로 도입된 App Router에서는 각 API 경로에 대해route.ts파일을 사용하는 것이 일반적입니다. 참고!(제가 맨날 영어 읽기 귀찮아서 공식 문서 제대로 안읽고 비슷하게 생긴 것들 그대로 긁어오다가 왜 안되지? 하고 머리 싸매는 사람이라. . 적어봅니다)
로그인 구현을 하고 나면 useSession 이라는 훅을 통해서 유저의 로그인 정보를 받아올 수 있는데요, 컴포넌트에서 이 HOOK 을 사용하기 위해서는 가장 상위 컴포넌트를 SessionProvider 로 감싸주어야합니다.
import { SessionProvider } from "next-auth/react"
export default function App({
Component,
pageProps: { session, ...pageProps },
}) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
)
}
카카오, 네이버, 구글 등 각종 소셜로그인 API 홈페이지에 들어가서 앱 등록을 하고, ClientID, ClientSecret 코드를 발급 받습니다.
Kakao API 홈페이지 , Naver API 홈페이지
NextAuth 에서 제공하는 Built-in providers 들은 다음과 같습니다. 공식사이트에서 각각 provider 별로 문서를 확인할 수 있으니 참고해보세용

GOOGLE_CLIENT_ID = ""
GOOGLE_CLIENT_SECRET = ""
NAVER_CLIENT_ID = ""
NAVER_CLIENT_SECRET = ""
💡개발 초보라면 이런 실수도 가능하다!
제가 자주 했던 실수인데요..-저 로그인이 안되네요 ㅠㅠ
-엇 제 컴퓨터에서는 잘 되는데 .. !보통 이런 문자를 주고받게된다면 꼭 문제는 .env 파일때문이더라고요 ..
환경변수 설정하는 거 까먹고 팀원들한테 연락했다가 민망했던 경험이 꽤나 있었어요 저는 .. (바보).env(환경변수 설정 파일) 은 민감한 정보를 저장하고있기 때문에 깃허브에 올리지 않아요. 그래서 누군가 작업한 것을 pull 받을 때나, 배포 환경에서는 꼭 직접 환경변수 세팅을 해주어야합니다.
전 이번에 처음으로 vercel 을 이용해서 혼자 배포를 해보았는데 또 환경 변수 세팅하는걸 까먹어버렸어요 😅 (바보 2)
그리고 환경변수에 오타는 없는지 한번 더 확인해보세요!
처음 네이버 로그인 개발 할 때 client secret 을 '보기' 버튼을 안 누른채로 ctrl+c , ctrl+v 를 했는데 알고보니 제대로 복사가 안되었더라고요 .. client ID 값이 동일하게 Client Secret 에도 저장이 되어서 로그인에 자꾸 실패했는데, 뭐가 잘못된줄도 모르고 왜 안되나 한참 고민했어요 ... ㅎ (바보 3)
nextauth 에서 build in 으로 제공해주는 provider 를 import 한 후에, providers 객체 안에 배열 형태로 넣어주면 됩니다.
import TwitterProvider from "next-auth/providers/twitter"
import NextAuth from "next-auth"
const handler = NextAuth({
//여기에 옵션 추가!
...
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
NaverProvider({
clientId: process.env.NAVER_CLIENT_ID!,
clientSecret: process.env.NAVER_CLIENT_SECRET!,
}),
KakaoProvider({
clientId: process.env.KAKAO_CLIENT_ID!,
clientSecret: process.env.KAKAO_CLIENT_SECRET!,
allowDangerousEmailAccountLinking: true,
}),
],
...
})
export { handler as GET, handler as POST }
NextAuth 안에는 providers 외에도 pages, callbacks 등 다양한 옵션을 넣을 수 있는데요. 이부분은 다른 게시글로 다뤄보도록 하겠습니다.
일단 저는 이렇게 작성했습니다!
session: {
strategy: "jwt" as const,
maxAge: 60 * 60 * 24,
updateAge: 60 * 60 * 2,
},
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
NaverProvider({
clientId: process.env.NAVER_CLIENT_ID!,
clientSecret: process.env.NAVER_CLIENT_SECRET!,
}),
KakaoProvider({
clientId: process.env.KAKAO_CLIENT_ID!,
clientSecret: process.env.KAKAO_CLIENT_SECRET!,
allowDangerousEmailAccountLinking: true,
}),
],
pages: { signIn: "/login" },
callbacks: {
session: ({ session, token }) => ({
...session,
user: {
...session.user,
id: token.sub,
},
}),
jwt: async ({ user, token }) => {
if (user) {
token.sub = user.id;
}
return token;
},
},
adapter 부분은 아래 7번에서 설명드릴게요
기본적으로 NextAuth.js는 세션, 사용자, 인증 제공자 정보 등을 메모리에 저장하지만, 이렇게 저장된 데이터는 애플리케이션이 실행되고 있는 동안만 유지됩니다.
그래서 어댑터를 사용하여 세션 데이터를 외부 데이터베이스(MongoDB, PostgreSQL, MySQL 등)에 저장해주어야합니다!
저는 prisma 를 사용했는데요,
NextAuth 에서는 Prisma 외에도 Firebase , Supabase, MongoDB 등 다양한 어댑터를 제공하고있으니 개발 환경에 맞게 공식문서를 참고하시면 됩니다.
우선 공식문서에서 시키는대로 필요한 것들을 설치합니다.
npm install @auth/prisma-adapter
(prisma 기본 세팅이 되어있는것을 전제로 합니다. prisma 초기 세팅하는 법은 또 다른 게시물로 다루도록 하겠습니다)
그리고 app/api/auth/[...nextauth]/route.ts 이 파일에서 adapter 옵션을 추가해줍니다.
import TwitterProvider from "next-auth/providers/twitter"
import NextAuth from "next-auth"
//adapter import
import { PrismaAdapter } from "@auth/prisma-adapter";
const handler = NextAuth({
//adapter 옵션 추가
adapter: PrismaAdapter(prisma),
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
}),
NaverProvider({
clientId: process.env.NAVER_CLIENT_ID!,
clientSecret: process.env.NAVER_CLIENT_SECRET!,
}),
KakaoProvider({
clientId: process.env.KAKAO_CLIENT_ID!,
clientSecret: process.env.KAKAO_CLIENT_SECRET!,
allowDangerousEmailAccountLinking: true,
}),
],
...
})
export { handler as GET, handler as POST }
마지막으로 prisma/schema.prisma 파일에서 User, Account, Session 모델만 넣어주면 끝!
아래 코드는 공식문서에서 제공하는 샘플인데, 적절하게 수정해서 사용하시면 됩니다.
model User {
id String @id @default(cuid())
name String?
email String @unique
emailVerified DateTime?
image String?
accounts Account[]
sessions Session[]
// Optional for WebAuthn support
Authenticator Authenticator[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Account {
userId String
type String
provider String
providerAccountId String
refresh_token String?
access_token String?
expires_at Int?
token_type String?
scope String?
id_token String?
session_state String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@id([provider, providerAccountId])
}
model Session {
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
*주의!!
카카오 소셜로그인을 사용한다면 Account 모델에 refresh_token_expires_in 속성을 추가해주어야합니다! (type: Int)
model Account {
id String @id @default(cuid())
userId String @map("user_id")
type String?
provider String
providerAccountId String @map("provider_account_id")
token_type String?
refresh_token String? @db.Text
refresh_token_expires_in Int?
access_token String? @db.Text
expires_at Int?
scope String?
id_token String? @db.Text
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountId])
@@map("accounts")
}
드디어 마지막 단계입니다!
로그인 페이지, 버튼 등 특정 컴포넌트를 클릭했을 때 signIn() 함수와, 사용한 소셜 로그인 이름을 string 타입으로 전달해주면 끝!
로그아웃은 signOut() 호출해주면 끝!
import { signIn } from "next-auth/react"
export default () => (
<button onClick={() => signIn("google")}>Sign in with Google</button>
)
로그인 후 받아온 로그인 정보는 useSession() 훅을 불러와서 사용할 수 있습니다.
session.user ~~ 를 가지고 저장된 유저 정보를 받아올 수 있구요, status 를 가지고 현재 로그인 상태를 확인할 수 있습니다.
import { useSession } from "next-auth/react"
export default function Component() {
const { data: session, status } = useSession()
if (status === "authenticated") {
return <p>Signed in as {session.user.email}</p>
}
return <a href="/api/auth/signin">Sign in</a>
}
클라이언트에서 사용할 수 있는 다양한 메서드들은 공식문서를 통해 확인해보세요
next auth 에 익숙하지만 구현 방법이 기억이 안나서 들어오신 분들을 위한 한장 요약 사진입니다.