5. 게시판(Post) 사이트 / 로그인 구현(NextAuth.js)

박준혁·2023년 6월 5일
0

동아리에 대한 전반적인 소개와 설명을 담은 메인페이지 구현이 마무리 되었으니 이번에는 동아리 부원들이 회의록 작성 및 게시판 용도로 사용 할 수 있는 사이트를 구현해보고자 한다. 그러기 위해서는 가장 먼저 부원들이 로그인 할 수 있도록 로그인 기능을 구현할 계획이다.

게시판 사이트 제작

게시판 사이트에 대하여 전체적인 구성, 레이아웃을 계획해볼 것이고 이후에 어떠한 기능들을 추가할 것인지에 대하여 작성해보고자 한다.

Post Layout

먼저 저번에 특별한 링크 없이 제작해둔 Log In 버튼을 눌렀을 때 로그인 할 수 있는 창으로 이동한다. 해당 창에서 로그인을 성공한다면 게시글을 확인하고 작성할 수 있는 '/post' 사이트로 이동한다. post 사이트는 아래 그림의 형태이다.

따라서 Next.js의 라우팅 기능을 이용해 post 사이트를 먼저 임시로 제작하였다. 내부에 post라는 폴더를 만들고, 해당 폴더 내 page.js 파일에 임시로 파일을 제작하였다. /post로 이동하면 사이트 이동하는 것을 확인할 수 있다.

Post Feature

조금 더 구체적으로 Post 사이트에서 구현할 기능들을 정리해보고자 한다.

  • 로그인 기능
  • 비밀번호 변경
  • 게시글 작성 기능 구현
  • 게시글 확인 & 수정/삭제 기능 구현 (수정/삭제 기능은 권한에 따라)

로그인 구현

NextAuth.js 사용

로그인을 구현하는 방식은 NextAuth.js를 이용해서 구현해보고자 한다. 먼저 아래와 같은 명령어를 이용해 라이브러리를 설치한다. 두번째 명령어는 mongodb adapter를 설치하는 명령어이다.

npm install next-auth
npm install @next-auth/mongodb-adapter 

MongoDB 사용 코드

앞으로 사이트의 다양한 정보를 저장하는 용도로 MongoDB를 사용할 것이다. 그 정보 중 이번에 구현하는 동아리 부원들의 정보(아이디, 비밀번호, 이름, 소속 등)도 MongoDB에 저장하게 된다. 먼저 MongoDB에 연결하는 기본 코드는 아래와 같다. 해당 코드는 database.js라는 이름으로 따로 저장한 뒤, DB에 연결해야하는 상황에서 import해서 계속 사용할 예정이다.

//database.js

import { MongoClient } from 'mongodb'
const url = 'MongoDB URL'
const options = { useNewUrlParser: true }
let connectDB

if (process.env.NODE_ENV === 'development') {
  if (!global._mongo) {
    global._mongo = new MongoClient(url, options).connect()
  }
  connectDB = global._mongo
} else {
  connectDB = new MongoClient(url, options).connect()
}
export { connectDB }

SignIn, SignOut 구현

해당 라이브러리를 이용해 signIn과 signOut을 자동적으로 이용할 수 있다. 'next-auth/react' 라이브러리 내부에 signIn과 signOut 함수를 사용하면 되고, 각각의 함수 내부에 callbackUrl를 지정하면, 로그인과 로그아웃 이후에 어떤 주소로 이동하는지 지정할 수 있다. 이 버튼들은 onClick function이 필요하여 해당 기능은 새로 파일을 제작하여 'use client'를 사용하며, 구현한 버튼을 export하여 다른 파일에서 사용한다.

'use client'

import { signIn, signOut} from 'next-auth/react'

function LoginBtn(){
    return(
        <button onClick={()=>{signIn({callbackUrl:"/post"})}}>Log In</button>
    )
}
function LogoutBtn(){
    return(
        <button onClick={()=>{signOut({callbackUrl:"/"})}}>Log Out</button>
    )
}

CredentialsProvider 사용 방법

Next-auth에서는 다양한 방식의 로그인 방식을 제공하고 있다. 여러 사이트의 아이디/비밀번호를 이용해서 로그인 가능한 소셜 로그인 방식을 제공하고 있지만 이번에 사용해볼 방식은 Credentials Provider라고 부르는 사이트 자체 아이디/비밀번호 를 이용해서 로그인하는 방식이다. 해당 방식을 사용하기 위해 구체적인 사항들을 지정해야하며, 그 내용은 pages/api/[...nextauth].js 루트로 파일을 제작하여 그 내부에 아래와 같은 코드를 작성하면 된다.

export const authOptions = {
  providers: [
    CredentialsProvider({
      //LogIn Page
      name: "credentials",
        credentials: {
          username: { label: "Username", type: "text" },
          password: { label: "Password", type: "password" },
      },

      //Authorize
      async authorize(credentials) {
        let db = (await connectDB).db('vessweb');
        let user = await db.collection('user').findOne({username : credentials.username})
        if (!user) {
          console.log('No ID Information');
          return null
        }
        const pwcheck = await bcrypt.compare(credentials.password, user.password);
        if (!pwcheck) {
          console.log('Password Error');
          return null
        }
        return user
      }
    })
  ],

해당 코드는 NextAuth.js Document에서 제공하는 방법을 기초로 하고 있다. 첫번째 코드 부분은 LogIn Page를 만드는 과정이고 두번째 코드 부분은 로그인 유효성을 확인하는 방법이다.

LogIn Page

첫번째 코드 부분인 로그인 페이지에서는 크게 username(사용자명)과 password(비밀번호)를 받기 때문에 각각의 type과 label을 지정하였다.

Authorize

두번째 코드 부분은 authorize, 즉 로그인의 성공 여부를 확인하는 과정이다. 먼저, 로그인 정보가 vessweb이라는 db 내부 user라는 collection에 저장되어있기 때문에 해당 경로에 접근해 입력한 username과 동일한 username이 존재하는지 확인하고, 없다면 username이 없다는 메세지와 null을 return한다. 만약 존재한다면 비밀번호를 비교할 차례이다. 비밀번호는 db에 암호화해서 저장하기 때문에 bcrypt.compare를 이용해 암호화한 정보와 일치하는지 확인하는 과정을 거친다. 만약 다르다면 비밀번호가 다르다는 메세지와 null을 return하고, 같다면 db에서 가져온 user 정보를 return한다.

추가 내용 설정

아래 코드는 추가적인 내용들을 설정하는 코드이다. credentials 로그인 방식은 항상 jwt 방식을 사용하는데 jwt 방식에 대한 설명은 여기서는 생략하겠다. jwt에 user의 정보 대부분을 저장하기 위해 jwt가 생성되는 순간 user의 정보 대부분을 저장한다. 다음 session에서는 유저 세션이 조회될 때 호출되는 함수로, 앞으로 유저 정보를 불러오기 위해서 세션을 호출할 일이 굉장히 많은데 token에 저장된 user정보를 session으로 넘겨주어 유저 정보를 확인하는 과정에서 이상이 없도록 하는 역할을 수행한다.

  callbacks: {
    jwt: async ({ token, user }) => {
      if (user) {
        token.user = {};
        token.user.username = user.username
        token.user.name = user.name
        token.user.role = user.role
        token.user.semester= user.semester
        token.user.group = user.group
        token.user.team = user.team
        token.user.teamname = user.teamname
      }
      return token;
    },
    session: async ({ session, token }) => {
      session.user = token.user;
      return session;
    },
  },

메인 페이지 로그인 화면

앞서 제작한 client rendering 방식의 버튼 2가지를 사용하고자 한다. 기존에 작성한 로그인 버튼 대신 제작한 LogIn Button을 사용하였다. 하지만 반대로 로그인 이후에는 버튼이 LogOut으로 바뀌어야 한다. 이를 위해서 "session"을 호출하여 현재 사용자가 로그인 상태인지 확인해야 한다. server rendering 방식에서는 아래와 같은 방식으로 session을 호출해 로그인했는지 확인할 수 있다.

import { authOptions } from "@/pages/api/auth/[...nextauth].js"
import { getServerSession } from "next-auth"

export default function Page(){
  let session = await getServerSession(authOptions)
  if (session) {
    //로그인 성공한 상태
  }
  else{
    //로그인 실패한 상태
  }

따라서 session을 호출하여 그 값에 따라서 로그인이 안 되었을 때는 LoginBtn, 되었을 때는 LogoutBtn을 각각 호출한다.

{session ? <LogoutBtn/>:<LoginBtn/>}

추가적으로 사용자가 로그인 되어있을 때 post(게시판)으로 들어갈 수 있는 버튼을 더하였다.

{session ? <a href='/post'>📝</a>:null}

그 결과 아래와 같이 구현할 수 있었다. 로그인 하기 이전 메인 페이지 화면이다.

로그인 성공 이후 메인 페이지 화면이다.

로그인 화면 구현 결과

로그인 화면은 아래와 같다.

임시 로그인 테스트를 위해 mongoDB의 user collection에 아이디, 이름, 암호화된 비밀번호 등을 저장하고 console.log한 결과 회원 정보가 잘 출력됨을 확인할 수 있었다. 앞으로 레이아웃 구현과 구체적인 게시글 관리 방식, 로그인 한 정보 활용법 등을 구현하고자 한다.

0개의 댓글