[Next.js] MongoDB & Next-aut 회원가입

dee·2022년 12월 25일
2

Next.js

목록 보기
3/3
post-thumbnail

🗃️ 시작. Database 선택

Next.js 프로젝트의 UI를 모두 마치고 이제 기능 구현을 시작해본다.
먼저 프로젝트 데이터 바인딩을 위해 데이터 저장소로 firebase와 MongoDB 중에서 고민을 했다. 맨 처음 익숙한 firebase로 저장소를 선택하려고 했었다. 하지만 찾아보니 firebase는 사용자의 생명주기, 즉 로그인, 로그아웃 등을 프론트엔드 쪽에서 처리하도록 가정하고 있다고 한다. 그러다보니 Next.js 의 GetServerSideProps 같은 함수를 사용할 때 사용자 인증 정보를 접근하기가 자유롭지 않다고 한다. 이에 MongoDB를 데이터베이스로 선택하였다.

참고 자료 : Next.js에 Firebase 연결하기


목표. 회원가입 및 로그인 기능 구현

  • jwt를 사용하여 로그인 및 세션 유지
  • password 암호화하여 저장 및 비교
  • DB에 users collection추가하여 저장
  • 구글 로그인 및 회원가입

Chapter 1. 개발 환경 구축

  1. MongoDB cluster 생성.
  1. MongoDB 및 Next-auth 설치.
  • next-auth : next에서 사용되는 패키지의 일종으로 로그인과 같은 사용자 인증 기능을 제공. (깃허브, 구글, 페이스북 등의 인증 옵션 제공)
  • mongodb : Mongo DB를 연동하기 위한 모듈.
  • bcryptjs : 비밀번호를 암호화하기 위한 라이브러리.
    npm install next-auth mongodb bcryptjs
  1. env 설정
  • mongodb에 접근하기 위해서는 아래와 같은 URI가 필요하다. 또한 아무나 접근하면 안되기에 추가적인 정보가 필요하다. 보통 username, password 정도만으로도 충분하지만 나는 생성한 doWork 데이터베이스에 접근하기 위해 databasename를 추가하여 작성했다.
    MONGODB_URI = mongodb+srv://<username>:<password>@cluster0.s0pc9fn.mongodb.net/<databasename>?retryWrites=true&w=majority
  • 위의 username과 password를 가져오는 방법을 정리해보자. 아래 메뉴 중 Database Access(1)를 선택 후, 2번 버튼을 클릭하여 user를 추가한다. 이 때 설정한 username과 password를 위의 MONGODB_URI에 적어주면 된다. 또한 이 user가 관리자가 되어야하므로 Built-in Role을 Atlas admin으로 설정해주어야 오류가 생기지 않는다.

Chapter 2. 기능 구현

👩‍💻 회원가입.

  1. api/auth 안에 signup.ts생성.
    • Next.js는 pages/api폴더 안에 있는 파일이 서버단에서 동작하게 된다. 폴더명과 파일명이 url이 되고 serverless로 구현이 된다.
  2. request method가 POST인지 확인.
  3. MongoClient의 connect 메서드를 통해 env에 설정한 MONGODB_URI로 db 연결을 한다.
  4. findOne 메서드를 사용하여 email이 이미 있는 지 확인 후 collection에 저장.
    • findOne에 조건을 매개변수로 넘겨줄 수 있음. 매개변수가 없으면 첫번째 요소 반환.
  5. insertOne으로 users collection에 사용자를 추가한다.
    • password는 'bcryptjs'의 hash를 사용하여 암호화한 후 db에 저장한다.
// api/auth/signup.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { MongoClient } from 'mongodb';
import { hash } from 'bcryptjs';

if (!process.env.MONGODB_URI) throw new Error('env error');
const uri: string = process.env.MONGODB_URI;

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method === 'POST') {
    const { email, password, ...restInfo } = req.body;
	
    // MongoDB 연결
    const client = await MongoClient.connect(uri);
    const db = client.db();
	
    // 기존의 가입된 이메일 체크하기
    const checkExisting = await db.collection('users').findOne({ email });

    if (checkExisting) {
      client.close();
      res.status(422).json({ result: false, error: '이미 가입된 계정이에요!' });
      return;
    }

    const status = await db.collection('users').insertOne({
      email,
      // 비밀번호 암호화
      password: await hash(password, 12),
      ...restInfo,
    });
	
    // 성공시 response
    res.status(201).json({ result: true, message: 'User created', ...status });
    client.close();
  } else {
    res.status(500).json({ result: false, error: 'Route not valid' });
  }
}

🧐 비밀번호는 왜 암호화해야하나?
해커나 사이버 범죄로 부터 안전하게 보호하기 위해 암호화를 한다. 하지만 이것이 강력한 보안이라는 생각은 금지!! 암호화는 취약점에 의해서 정보가 탈취되었더라도 그 데이터의 정확한 정보를 확인하기 힘들게 하는 의미가 더 강하다.


🔐 이메일 및 구글 로그인.

  1. api/auth폴더 안에 [...nextAuth].ts 파일 생성.
  2. providers : googleProvider, CredentialsProvider 등 로그인 관련 provider를 추가할 수 있다.
    • OAuth를 이용하기 위해서는 관련 페이지에 가서 도메인을 추가하고 clientId를 발급받아서 입력해주어야 한다.
  3. pages는 NextAuth가 제공하는 기본 페이지가 아닌 내가 만든 페이지로 로그인을 하기 위해 설정하는 것이다.
  4. callbacks로 jwt와 session도 간단하게 설정할 수 있다. 이 부분에 대해서는 좀 더 공부를 해봐야겠다.
// api/auth/[...nextAuth].ts
export default NextAuth({
  session: {
    strategy: 'jwt',
  },
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET,
    }),
    CredentialsProvider({
      name: 'credentials',
      credentials: {
        email: { label: 'email', type: 'text', placeholder: '이메일을 입력해주세요.' },
        password: { label: 'password', type: 'password', placeholder: '비밀번호를 입력해주세요.' },
      },
      async authorize(credentials) {
        const client = await MongoClient.connect(uri);

        const user = await client.db().collection('users').findOne({
          email: credentials!.email,
        });

        // ......
        client.close();
        return { id: user!.id };
      },
    }),
  ],
  callbacks: {
    async jwt({ token, account }) {
      if (account?.provider === 'google') {
        const client = await MongoClient.connect(uri);

        const user = await client.db().collection('users').findOne({
          email: token.email,
        });

        if (!user) {
          client.close();
          throw new Error('함께하고 있는 계정이 아니에요:(');
        }
      }
      return token;
    },
    session({ session }) {
      return session;
    },
  },
  secret: process.env.NEXTAUTH_SECRET,
  pages: {
    signIn: '/login',
  },
});

🍑 오늘의 공부 일기

Next.js, MongoDB, Next-auth를 연결하면서 지금도 헷갈리지만 처음에 아무것도 모르고 접근했을 때 정말 막막했다. 특히 [...nextAuth].ts 파일 설정이 어려웠다. 블로그를 참고하면서 했는데 최근에 v4로 업데이트되면서 없어진 항목들도 있어 어려움이 있었다. 또한 로그인과 회원가입에 모두 호출하여 사용하는 줄 알고 초반에 헷갈렸다. 두 개를 조금은 분리하여 바라보니 전체적인 흐름이 머릿속으로 그려지면서 조금씩 파악할 수 있었다. 그리고 흐름이 이해되너 NextAuth 문서도 수월하게 이해가 되었다. NextAuth안에 항목들이 정확하게 어떻게 작동하는지를 더 공부해보면서 코드를 리팩토링해나가야겠다.


참고자료
https://defineall.tistory.com/1117
https://velog.io/@1-blue/%EC%9D%B8%EC%A6%9D-%EB%A1%9C%EC%A7%81-%EA%B5%AC%ED%98%84-next-auth

profile
웹 프론트엔드 개발자

0개의 댓글