Next.js 프로젝트의 UI를 모두 마치고 이제 기능 구현을 시작해본다.
먼저 프로젝트 데이터 바인딩을 위해 데이터 저장소로 firebase와 MongoDB 중에서 고민을 했다. 맨 처음 익숙한 firebase로 저장소를 선택하려고 했었다. 하지만 찾아보니 firebase는 사용자의 생명주기, 즉 로그인, 로그아웃 등을 프론트엔드 쪽에서 처리하도록 가정하고 있다고 한다. 그러다보니 Next.js 의 GetServerSideProps 같은 함수를 사용할 때 사용자 인증 정보를 접근하기가 자유롭지 않다고 한다. 이에 MongoDB를 데이터베이스로 선택하였다.
참고 자료 : Next.js에 Firebase 연결하기
npm install next-auth mongodb bcryptjs
MONGODB_URI = mongodb+srv://<username>:<password>@cluster0.s0pc9fn.mongodb.net/<databasename>?retryWrites=true&w=majority
api/auth
안에 signup.ts
생성.// 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' });
}
}
🧐 비밀번호는 왜 암호화해야하나?
해커나 사이버 범죄로 부터 안전하게 보호하기 위해 암호화를 한다. 하지만 이것이 강력한 보안이라는 생각은 금지!! 암호화는 취약점에 의해서 정보가 탈취되었더라도 그 데이터의 정확한 정보를 확인하기 힘들게 하는 의미가 더 강하다.
api/auth
폴더 안에 [...nextAuth].ts
파일 생성.// 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