Compare password method
1
2
3
4
5
6
7
8
userSchema.methods.comparePassword = function (plainPassword, cb) {
bcrypt.compare(plainPassword, this.password, function (err, isMatch) {
if (err) {
return cb(err);
}
cb(null, isMatch);
});
};
사용자가 로그인 할 때 입력된 비밀번호와 DB의 암호화된 비밀번호를 비교해야 한다. comparePassword메소드를 만들어 준다. comparePassword메소드는 사용자가 로그인 시 입력한 plainPassword와 DB의 비밀번호를 비교한다. bcrypt.compare()는 true와 false를 반환하며, 비밀번호가 일치하면 true를 넘겨준다.
Login route and save token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
app.post("/login", (req, res) => {
// 요청된 이메일을 데이터 베이스에서 있는지 확인
User.findOne({ email: req.body.email }, (err, user) => {
if (!user) {
return res.json({
loginSuccess: false,
message: "제공된 이메일에 해당하는 유저가 없습니다",
});
}
// 요청한 이메일이 있으면 비밀번호 같은지 확인
// (err, ##)은 callback function
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch) {
// 비번이 틀렸을 때
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다.",
});
}
user.generateToken((err, user) => {
if (err) {
res.status(400).send(err);
}
// 토큰을 저장한다 쿠키나 로컬저장소, session에 저장 가능
// 쿠키에 저장
res
.cookie("x_auth", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
});
});
사용자 로그인에도 POST방식이 사용된다. 먼저 findOne메소드로 데이터베이스에 이메일이 있는지 확인한 후 존재하면 비밀번호를 비교한다. 위에서 만든 comparePassword메소드를 사용하고, isMatch를 받는다. isMatch가 true이면 사용자 토큰을 생성하고 false이면 오류 메세지를 출력한다.
1
2
3
4
{
"loginSuccess": true,
"userId": "61d149633c6b8b24d75b5e7e"
}
postman으로 /login에서 테스트한 결과이다. 데이터베이스에 회원 정보가 존재 하는 것을 알 수 있다.
User Token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
user.comparePassword(req.body.password, (err, isMatch) => {
if (!isMatch) {
// 비번이 틀렸을 때
return res.json({
loginSuccess: false,
message: "비밀번호가 틀렸습니다.",
});
}
user.generateToken((err, user) => {
if (err) {
res.status(400).send(err);
}
res
.cookie("x_auth", user.token)
.status(200)
.json({ loginSuccess: true, userId: user._id });
});
});
사용자가 로그인한 후 토큰을 만들어 저장해야 한다. 토큰은 cookie, session, local storage에 저장할 수 있다. 사용자 로그인 시 비밀번호가 일치하면 토큰을 저장한다. res의 cookie에는 cookie 이름과 사용자 토큰, status는 정상 동작을 의미하는 200을 포함해 전달한다.
토큰 생성에 필요한 의존성은 jsonwebtoken이고, cookie에 token을 저장하기 위한 의존성은 cookie-parser이다.
Auth
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// auth.js
let auth = (req, res, next) => {
// 인증 처리 관리
// 클라이언트 쿠키에서 토큰을 가져온다
let token = req.cookies.x_auth;
// 토큰을 복호화 한 후 유저를 찾는다
User.findByToken(token, (err, user) => {
if (err) throw err;
if (!user) return res.json({ isAuth: false, error: true });
req.token = token;
req.user = user;
// next의 이유는 middleware에서 더 진행할 수 있게 넣어준다
next();
});
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const jwt = require("jsonwebtoken");
userSchema.statics.findByToken = function (token, cb) {
const user = this;
// 토큰을 decode
jwt.verify(token, "secreteToken", function (err, decoded) {
// user id를 이요해서 user 찾은 다음에
// 클라이언트에서 가져온 토큰과 DB에 보관된 토큰이 일치하는지 확인
user.findOne({ _id: decoded, token: token }, function (err, user) {
if (err) return cb(err);
cb(null, user);
});
});
};
인증을 위해 middleware에 auth.js를 생성해 인증을 관리한다. 사용자가 로그인을 한 후 화면을 이동할 때 사용자의 정보가 유효한지 확인해 줘야 한다. 토큰을 사용해 인증을 하며 사용자의 토큰은 cookie에 저장되어 있다.
jsonwebtoken을 이용해 클라이언트에서 가져온 토큰과 데이터베이스에 보관되어 있는 토큰이 일치하는지 확인한다.
로그아웃
1
2
3
4
5
6
7
8
app.get("/api/users/logout", auth, (req, res) => {
User.findOneAndUpdate({ _id: req.user._id }, { token: "" }, (err, user) => {
if (err) return res.json({ success: false, err });
return res.status(200).send({
success: true,
});
});
});
로그아웃 기능을 구현하기 위해 로그아웃 route를 만들었다. Route의 기능은 로그아웃 하려는 유저를 데이터베이스에서 찾아 해당 사용자의 토큰을 지워준다. 토큰은 위에서 페이지의 로그인 된 회원의 인증을 위해 사용되었기 때문에 토큰을 지우면 로그인을 하지 않은 상태와 같게 된다.
findOneUpdate함수를 사용해 로그아웃 하려는 사용자의 토큰을 지우고 업데이트 해준다.
MongoDB의 사용자를 확인해보면 토큰이 ""으로 되어 있는 것을 볼 수 있다.
