회원가입과 로그인 기능을 구현하기 위해 Express와 MongoDB를 사용한다.
// server/model/User.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
minlength: 6,
},
name: {
type: String,
required: true,
unique: true,
},
});
const User = mongoose.model("User", userSchema);
export default User;
회원가입 기능에 필요한 유저 모델 스키마를 만들어준다.
// server/router/User.js
import User from "../model/User.js";
import pkg from "bcryptjs";
const router = Router();
const { genSalt, hash } = pkg;
router.post("/signup", async (req, res) => {
const { userId, password, name } = req.body;
try {
const user = new User({
userId,
password,
name,
});
const salt = await genSalt(10);
user.password = await hash(password, salt);
await user.save();
res.status(200).send("Success");
} catch (err) {
console.error(err.message);
res.status(500).send("Server Error");
}
});
클라이언트에서 아이디, 비밀번호, 이름을 받아와서 새 모델을 만들어 MongoDB에 저장한다.
이때 bcrypt의 genSalt, hash를 사용해서 비밀번호를 암호화해서 저장한다.
// server/model/User.js
import mongoose from "mongoose";
const userSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
minlength: 6,
},
name: {
type: String,
required: true,
unique: true,
},
token: {
type: String,
},
});
로그인 성공시 토큰을 생성하기 위해 모델 스키마에 token을 추가한다.
import bcrypt from "bcryptjs";
userSchema.methods.comparePassword = function (pwd, cb) {
const user = this;
bcrypt.compare(pwd, user.password, function (err, isMatch) {
if (err) return cb(err, false);
cb(null, isMatch);
});
};
비밀번호를 암호화해서 저장했기 때문에 입력된 비밀번호도 암호화한 뒤 비교해준다. 두 암호가 같다면 콜백으로 true를 반환한다.
import jwt from "jsonwebtoken";
userSchema.methods.generateToken = function (cb) {
const user = this;
const token = jwt.sign(user._id.toHexString(), "secretToken");
user.token = token;
user
.save()
.then(() => {
cb(null, user);
})
.catch((err) => {
cb(err, null);
});
};
jsonwebtoken을 사용해 토큰을 생성해 콜백함수로 넘겨준다.
// server/router/User.js
router.post("/signin", (req, res) => {
const { userId, password } = req.body;
User.findOne({ userId })
.then((docs) => {
if (!docs) {
return res.json({
loginSuccess: false,
messsage: "입력하신 ID에 해당하는 유저가 없습니다.",
});
}
docs.comparePassword(password, (err, isMatch) => {
if (!isMatch)
return res.json({
loginSuccess: false,
messsage: "비밀번호가 틀렸습니다.",
});
docs.generateToken((err, user) => {
if (err) return res.status(400).send(err);
res.cookie("x_auth", user.token).status(200).send("Success");
});
});
})
.catch((err) => {
return res.status(400).send(err);
});
});
로그인 성공시 토큰을 생성해서 쿠키에 저장한다.
로그인 성공시 로그인 상태를 유지하기 위해 토큰을 가지고 있는지 검사를 한다.
// server/middleware/auth.js
import User from "../model/User.js";
let auth = (req, res, next) => {
let token = req.cookies.x_auth;
User.findByToken(token)
.then((user) => {
if (!user) return res.json({ isAuth: false, error: true });
req.body.token = token;
req.body.user = user;
next();
})
.catch((err) => {
throw err;
});
};
export default auth;
클라이언트 쿠키에서 토큰을 가져와 복호화한 후 유저를 찾는다.
// server/models/User.js
import jwt from "jsonwebtoken";
userSchema.statics.findByToken = function (token) {
let user = this;
return jwt.verify(token, "secretToken", (err, decoded) => {
return user
.findOne({ _id: decoded, token: token })
.then((user) => user)
.catch((err) => err);
});
};
유저 아이디를 이용해 유저를 찾은 다음 클라이언트에서 가져온 토큰과 db에 저장된 토큰이 일치한지 확인한다.
// server/router/User.js
router.get("/auth", auth, (req, res) => {
res.status(200).json({
_id: req.body.user._id,
isAuth: true,
id: req.body.user.id,
name: req.body.user.name,
});
});
auth 미들웨어를 통과하면 유저 정보와 isAuth를 true로 넘겨준다.
// server/router/User.js
router.get("/logout", auth, (req, res) => {
User.findOneAndUpdate({ _id: req.body.user._id }, { token: "" })
.then(() => {
res.clearCookie("x_auth");
return res.status(200).send({ success: true, logout: "로그아웃 완료" });
})
.catch((err) => {
res.json({ success: false, err });
});
});
로그아웃은 간단하게 유저 아이디에 해당하는 토큰을 없애준다.