Next Auth로 소셜 로그인 구현하기

nnhw·2024년 10월 22일

NextAuth

목록 보기
1/1
post-thumbnail

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

처음 소셜로그인 기능을 개발하는 분들도 글만 쉽게 따라할 수 있도록 구현 방법, 제가 마주한 (아주 사소한) 오류들과 해결 방법을 기록해보았습니다.

글의 마지막에 한장 요약 사진도 올려두었으니 이전에 구현해 본 경험이 있다면 그 이미지만 참고하셔도 충분히 구현하실 수 있을 거예요!


목차

우선 전체적인 프로세스는 다음과 같습니다.

  1. 설치
  2. api 루트 추가하기
  3. sessionProvider 추가하기
  4. 홈페이지에서 로그인 api 사용 등록하기
  5. 환경변수 등록하기
  6. providers 옵션 추가하기
  7. 어댑터 세팅하여 외부 DB에 저장하기 (prisma)
  8. signIn() 함수 호출하기, useSession 사용하기

참고자료 : 공식문서 | NextAuth.js



1. Next Auth 설치하기

📍사용하는 패키지 매니저(npm, yarn, pnpm) 에 따라 적절한 방법으로 next-auth 를 설치합니다.


npm install next-auth

yarn add next-auth

pnpm add next-auth


2. API Route 추가하기

📍app/api/auth/[…nextauth]/route.ts 경로에 아래 코드를 추가해줍니다.


import NextAuth from "next-auth"

const handler = NextAuth({
  //여기에 옵션 추가! 
})

export { handler as GET, handler as POST }

2-1. API Route 추가 시 주의사항

  • 참고하신 블로그/강의 글과, 사용하고 계신 next js 버전이 같은지 확인하세요.
  • 버전에 따라 마지막 export default .. 부분, 파일 명 수정이 필요할 수도 있어요!

💡개발 초보라면 이런 실수도 가능하다!
개발 초보자의 경우, 여러가지 강의 영상이나 블로그 글을 찾아보면서 따라하시는 분들이 많으실텐데요. Next.js의 버전에 따라 export 하는 방식이 다릅니다.

공식 문서에서는 위와 같이 App Router를 사용하는 Next.js 13.2 이상 버전에서는 Route Handler 를 사용하라고 나와있어요.
공식문서 확인해보기 | NextAuth.js

pages 경로를 사용하는 공식 문서 예제에서 마지막 줄이 다르니 꼭 확인하세요.

그리고 여기서는 파일도 동적 라우팅 기능으로 […nextauth].js 를 생성했는데,
Next.js 13 이후로 도입된 App Router에서는 각 API 경로에 대해 route.ts 파일을 사용하는 것이 일반적입니다. 참고!

(제가 맨날 영어 읽기 귀찮아서 공식 문서 제대로 안읽고 비슷하게 생긴 것들 그대로 긁어오다가 왜 안되지? 하고 머리 싸매는 사람이라. . 적어봅니다)



3. Provider 추가하기

📍 상위 컴포넌트를 SessionProvider로 감싸줍니다.


로그인 구현을 하고 나면 useSession 이라는 훅을 통해서 유저의 로그인 정보를 받아올 수 있는데요, 컴포넌트에서 이 HOOK 을 사용하기 위해서는 가장 상위 컴포넌트를 SessionProvider 로 감싸주어야합니다.

import { SessionProvider } from "next-auth/react"

export default function App({
  Component,
  pageProps: { session, ...pageProps },
}) {
  return (
    <SessionProvider session={session}>
      <Component {...pageProps} />
    </SessionProvider>
  )
}


4. 소셜로그인 사용 등록하기

📍API 사용 등록과 환경변수 등록을 합니다.


카카오, 네이버, 구글 등 각종 소셜로그인 API 홈페이지에 들어가서 앱 등록을 하고, ClientID, ClientSecret 코드를 발급 받습니다.

Kakao API 홈페이지 , Naver API 홈페이지

NextAuth 에서 제공하는 Built-in providers 들은 다음과 같습니다. 공식사이트에서 각각 provider 별로 문서를 확인할 수 있으니 참고해보세용

공식문서 | NextAuth.js



5. env 파일에 Client ID, Client Secret 등록

📍최상위 폴더에 .env 파일을 생성하고, 홈페이지에서 발급받은 환경변수를등록합니다.


GOOGLE_CLIENT_ID = ""
GOOGLE_CLIENT_SECRET = ""
NAVER_CLIENT_ID = ""
NAVER_CLIENT_SECRET = ""

5-1. 환경변수 사용 시 주의할 점

  • .env 파일 확인했나요?
  • 배포 환경에 환경변수 등록 했나요?
  • 환경변수에 오타는 없는지 확인해보세요!

💡개발 초보라면 이런 실수도 가능하다!
제가 자주 했던 실수인데요..

-저 로그인이 안되네요 ㅠㅠ
-엇 제 컴퓨터에서는 잘 되는데 .. !

보통 이런 문자를 주고받게된다면 꼭 문제는 .env 파일때문이더라고요 ..
환경변수 설정하는 거 까먹고 팀원들한테 연락했다가 민망했던 경험이 꽤나 있었어요 저는 .. (바보)

.env(환경변수 설정 파일) 은 민감한 정보를 저장하고있기 때문에 깃허브에 올리지 않아요. 그래서 누군가 작업한 것을 pull 받을 때나, 배포 환경에서는 꼭 직접 환경변수 세팅을 해주어야합니다.

전 이번에 처음으로 vercel 을 이용해서 혼자 배포를 해보았는데 또 환경 변수 세팅하는걸 까먹어버렸어요 😅 (바보 2)

그리고 환경변수에 오타는 없는지 한번 더 확인해보세요!

처음 네이버 로그인 개발 할 때 client secret 을 '보기' 버튼을 안 누른채로 ctrl+c , ctrl+v 를 했는데 알고보니 제대로 복사가 안되었더라고요 .. client ID 값이 동일하게 Client Secret 에도 저장이 되어서 로그인에 자꾸 실패했는데, 뭐가 잘못된줄도 모르고 왜 안되나 한참 고민했어요 ... ㅎ (바보 3)



6. providers 옵션 추가하기

📍app/api/auth/[...nextauth]/route.ts 파일에 다음과 같이 providers 옵션을 추가해줍니다.


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번에서 설명드릴게요



7. Adapter 세팅하여 외부 DB에 저장하기

📍Adapter 설치 후 route 파일에 옵션 추가, schema를 추가합니다.


기본적으로 NextAuth.js는 세션, 사용자, 인증 제공자 정보 등을 메모리에 저장하지만, 이렇게 저장된 데이터는 애플리케이션이 실행되고 있는 동안만 유지됩니다.

그래서 어댑터를 사용하여 세션 데이터를 외부 데이터베이스(MongoDB, PostgreSQL, MySQL 등)에 저장해주어야합니다!

공식문서 | NextAuth.js

저는 prisma 를 사용했는데요,

NextAuth 에서는 Prisma 외에도 Firebase , Supabase, MongoDB 등 다양한 어댑터를 제공하고있으니 개발 환경에 맞게 공식문서를 참고하시면 됩니다.

우선 공식문서에서 시키는대로 필요한 것들을 설치합니다.

7-1. adapter 설치

npm install @auth/prisma-adapter

(prisma 기본 세팅이 되어있는것을 전제로 합니다. prisma 초기 세팅하는 법은 또 다른 게시물로 다루도록 하겠습니다)

7-2. route파일에 adapter 옵션 추가하기

그리고 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 }

7-3. schema에 user, accout, session 모델 추가

마지막으로 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
}

7-4. 카카오 로그인 사용 시 주의할 점

*주의!!

카카오 소셜로그인을 사용한다면 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")
}


8. signIn() 함수 호출하기, useSession 사용하기

📍컴포넌트 클릭 시 소셜로그인 이름과 함께 signIn() 함수를 호출합니다.


드디어 마지막 단계입니다!

로그인 페이지, 버튼 등 특정 컴포넌트를 클릭했을 때 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>
}

클라이언트에서 사용할 수 있는 다양한 메서드들은 공식문서를 통해 확인해보세요

공식문서 | NextAuth.js



9. 한장 요약


next auth 에 익숙하지만 구현 방법이 기억이 안나서 들어오신 분들을 위한 한장 요약 사진입니다.

next auth 소셜로그인 한장 요약

profile
웹 프론트엔드 취준생 🥔

0개의 댓글