6. Auth.js 사용한 소셜로그인

윤지영·2024년 3월 29일

1. Next-Auth (Auth.js) 라이브러리

- NextAuth 라이브러리 설치

npm install next-auth 

Next-auth 라이브러리를 사용하면 기본적으로 JWT 방식.
유저 세션데이터를 DB에 저장해두지 않고 JWT만 유저에게 보내고
유저가 로그인이 필요한 페이지 방문시 유저가 제출한 JWT만 검사해서 입장시켜주는 방식

- NextAuth :: initialization

  • /app/api/auth/[...nextauth]/route.ts

    import NextAuth from "next-auth"
    
    const handler = NextAuth({
      ...
    })
    
    export { handler as GET, handler as POST }

- github, google, kakao 키 발급

🌞 NextAuth :: OAuth

키 발급 방법
  • Github
    Github.com 로그인 ➡ 우측상단 Settings ➡ Developer settings ➡ New OAuth app 만들기
  • google cloud console
    구글 키 생성 참고페이지
  • kakao developers
    • 애플리케이션 추가하기 ➡ 새 프로젝트 등록
    • 카카오 로그인 ➡ Redirect URI 등록
      http://localhost:3000/api/auth/callback/kakao/
    • 플랫폼 ➡ web 사이트 도메인 등록 : http://localhost:3000
    • 카카오 로그인 ➡ 카카오 로그인 활성화
      카카오 로그인 ➡ 동의항목 ➡ 필요한 항목 선택하기
  • id, secret key : .env.local에 추가
  • board/src/app/api/auth/[...nextauth]\route.js
import NextAuth from "next-auth";
import { connectDB } from "@/utils/database";
import { MongoDBAdapter } from "@next-auth/mongodb-adapter";
import GithubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";
import KakaoProvider from "next-auth/providers/kakao";

const authOptions = {
  providers: [
    GithubProvider({
      clientId: process.env.GITHUB_ID,
      clientSecret: process.env.GITHUB_SECRET,
    }),
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
    KakaoProvider({
      clientId: process.env.KAKAO_CLIENT_ID,
      clientSecret: process.env.KAKAO_CLIENT_SECRET,
    }),
  ],
  secret: process.env.NEXTAUTH_SECRET, //jwt생성시쓰는암호
};

const handler = NextAuth(authOptions);

export { handler as GET, handler as POST, authOptions };

- SessionProvider 감싸기

SessionProvider

  • NextAuth.js 라이브러리의 일부로, 애플리케이션 내에서 사용자 세션을 관리하는 컴포넌트.
  • 로그인, 로그아웃 상태를 추적하고, 사용자의 세션 정보를 애플리케이션 전반에 걸쳐 유지한다.
  • 애플리케이션의 최상위에 위치하여, 애플리케이션 전체에서 사용자의 인증 상태와 세션 정보에 쉽게 접근할 수 있도록 한다.
  • 컨텍스트 제공: SessionProvider는 React의 컨텍스트(Context) API를 사용하여, 하위 컴포넌트들이 현재 사용자의 세션 정보에 쉽게 접근할 수 있도록 한다. 이를 통해 개발자는 어느 컴포넌트에서든지 사용자의 로그인 상태나 세션 데이터를 활용할 수 있다.
  • useSession이라는 리액트 훅을 통해 접근하며, 주로 layout에 세션 컨텍스트인 로 감싸준다.
    단, SessionPrvider는 클라이언트 컴포넌트에서 사용하기 때문에 별도의 AuthProvider 컴포넌트를 생성하여 SessionPrvider로 감싼 뒤 layout에 넣어주었다.
// board/src/components/provider/AuthProvider.js

"use client";
import { SessionProvider } from "next-auth/react";

export default function AuthProvider({ children }) {
  return <SessionProvider>{children}</SessionProvider>;
}

// board/src/app/layout.js

import { Inter } from "next/font/google";
import "./globals.css";
import AuthProvider from "../components/provider/AuthProvider";
import Header from "@/components/Header";

const inter = Inter({ subsets: ["latin"] });

export const metadata = {
  title: "✨게시판 프로젝트",
  description: "게시판 ~~~~",
};

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <AuthProvider>
          <Header />
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

- login/logout button, useSession

  • useSession은 client Component에서 사용
  • useSession을 통해 로그인된 유저 정보를 출력
  • 조건부 렌더링
    • 현재 로그인된 유저 정보가 있을 경우
      유저 프로필,이름 & 로그아웃 버튼 노출
      • 카카오 로그인 시 이름이 노출되도록,
      • 깃헙, 구글 로그인 시 메일주소가 노출되도록
      • 카카오에만 email속성이 없어서 session.user.email이 존재할 경우 email 노출, 없을 경우 name을 노출시켰다.
    • 유저 정보가 없을 경우 : 로그인버튼 노출
// board/src/components/AuthButton.js
"use client";

import TextButton from "./UI/TextButton";
import { useSession, signIn, signOut } from "next-auth/react";

export default function AuthButton({}) {
  const { data: session } = useSession();
  if (session) {
    console.log(session);
    return (
      <div className="flex">
        <img
          className="avatar"
          src={session.user.image}
          alt={session.user.name}
        />
        {session.user.email ? (
          <span className="px-1">{session.user.email}</span>
        ) : (
          <span className="px-1">{session.user.name}</span>
        )}

        <TextButton
          text="logOut"
          onClickFn={() => {
            signOut();
          }}
        />
      </div>
    );
  }
  return (
    <>
      <TextButton text="logIn" onClickFn={() => signIn()} />
    </>
  );
}

2. OAuth + session방식

adapter

npm install @next-auth/mongodb-adapter 
  • board/src/app/api/auth/[...nextauth]/route.js
import { connectDB } from "@/utils/database";
import { MongoDBAdapter } from "@next-auth/mongodb-adapter";

const authOptions = NextAuth({
  providers: [
    //깃헙,구글,카카오 등..
  ],
  secret: process.env.NEXTAUTH_SECRET, 
  //어댑터 추가!
  adapter: MongoDBAdapter(connectDB),
});
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

어댑터 추가해 준 뒤 로그인 하면 몽고db에 3개의 폴더가 생성된다.

  • sessions : 현재 로그인된 유저 정보, 로그인 유효기간 포함
  • users는 유저(이메일로 구분)
  • accounts는 유저 계정
    하나의 유저는 여러개의 계정을 가지고 있을 수 있음.
    유저는 1명이지만 계정은 2개 이상 생성이 가능(이메일이 같으면 같은 유저라고 자동으로 간주)

글 작성 시 user 정보 db에 저장

  • getServerSession(authOptions); 사용하여 유저의 세션정보 가져와서, 유저가 글을 등록할 때 유저 이름, 이메일, 프로필 이미지 모두 db에 저장되도록 수정해주었다.

  • 로그아웃 상태일 때 401에러를 응답하도록 하였는데, 이후 추가적으로 글 작성 페이지에서 로그인하지 않은 유저는 로그인페이지로 리다이렉트 시켜주었다.

// board\src\app\api\new\route.js

import { connectToDatabase } from "@/utils/database";
import { NextResponse } from "next/server";
import { getServerSession } from "next-auth";
import { authOptions } from "../auth/[...nextauth]/route";

export async function POST(req) {
  const { postCollection } = await connectToDatabase();
  const session = await getServerSession(authOptions);
  console.log(session);
  if (!session) {
    return NextResponse.json(
      { error: "로그인이 필요합니다." },
      { status: 401 }
    );
  }
  try {
    const data = await req.json(); 
   	// 생략..

    const postData = {
      ...data,
      email: session.user.email,
      name: session.user.name,
      image: session.user.image,
    };
    console.log(postData);

    await postCollection.insertOne(postData);
    return NextResponse.json(postData);
  } catch (error) {
   // 에러처리
  }
}

수정 및 삭제 권한

  • 기존 ListItem 컴포넌트는 로그인 여부와 관계 없이 각 게시물의 수정/삭제 버튼이 모두에게 노출되었고, 권한도 부여되었다.
  • 권한있는 유저만 수정/삭제 하도록 조건부 렌더링으로 수정하였다.
    useSession을 통해 유저의 세션정보의 email과, props로 받은 userInfo의 email속성과 비교하여 이 두개가 동일할 경우에만 수정/삭제 버튼을 노출시키고, 로그인한 유저와 글쓴이가 다른 경우에는 작성자의 프로필이미지와 이름을 노출시키도록 수정 하였다.
import { useSession } from "next-auth/react";

export default function ListItem({ userInfo, id }) {

const { data: session } = useSession();
const isAuthor = session?.user?.email === userInfo.email;

 return (
         {isAuthor ? 
          ({/*수정삭제아이콘 노출 */})
           : 
          ({/*userInfo.image, userInfo.name 노출 */})
         }
)}

🔥 오늘의 트러블슈팅

👿 error 1

에러메시지가 왕창..
next-auth/mongodb-adapter 패키지가 mongodb 버전 5.x 또는 4.x를 요구하기 때문에 충돌이 발생.
mongodb 4.0버전으로 재설치..

npm uninstall mongodb
npm install mongodb@4

👿 error 2

사용자가 처음에 GitHub으로 가입했고, 같은 이메일로 Google을 통해 가입하려고 시도할 때 발생

err

NextAuth에서 제공하는 GitHub, Google, Kakao 같은 여러 소셜 로그인 방법을 사용할 때, 하나의 이메일 주소에 여러 제공자를 연결하는 것은 기본적으로 지원되지 않는다. 보안상의 이유로 자동으로 계정을 연결하는 기능을 제공하지 않기 때문..

✨ 해결

관련 이슈 : stackoverflow
공식 문서 : allowDangerousEmailAccountLinking: true

allowDangerousEmailAccountLinking: true,추가해주면 해결!

const authOptions = {
  providers: [
    GithubProvider({
	// 생략...
      allowDangerousEmailAccountLinking: true,
    }),   
  ], 
};

:: 진행상황 & TODO ::

  • 몽고 DB setting

  • MongoDB 입출력

  • 글 목록 조회 기능(/list)

    • 글 제목,~~
    • 날짜 데이터바인딩
  • 글 상세 페이지(/detail/[id]/page.js)

    • 제목, 내용,
    • 게시 날짜, 댓글 데이터바인딩
  • 글 작성 페이지

  • 글 수정 페이지

  • 글 삭제 페이지

  • 404페이지 만들기

  • error 페이지 만들기

  • loading 페이지 만들기

  • Next-auth 회원인증기능

  • S3 파일업로드

  • 글 작성 페이지 마크다운 작성페이지로 변경

  • 캐싱, 에러처리 등 부가기능

  • 스타일링

  • AWS 클라우드 배포

profile
쑥쑥쑥쑥 레벨업🌱🌼🌳

0개의 댓글