지금 까지 만든건 다 한번씩 해본거다.
그래서 처음 사용 했을때보다 좀더 빨리 만들수 있었다.
전에 써봤던 route
,get
,post
를 쓰고 있고 req.body
도 사용해봤다.
mongoose
도 마찬가지이고 render
도 하고 있다.
보다시피 이 모든걸 같이 구현하게 되면 정말 멋진 기능을 만들어 낼수 있다.
예상했겠지만 가장 중요한 controller
중 하나는 post
이다.
getJoin controller
는 페이지를 render
만 하지만
postJoin
은 실제로 사용되어지는 기능을 담당하고 있다.
데이터를 다루고 에러 처리, 유효성 체크 등을 하면서 DB
와 통신하고 있다.
지금까지 배웠던 모든 것들은 이렇게 개발할 수 있게 만들기 위한 준비 과정이었다.
usersController
에서 모든걸 사용하고 있다. mongoose
,middlewares
,redirections
,urls
...
controllers
,post
,get
,body
.. 이런 것들을 다 쓰고 있다.
지금 이 모든걸 사용하는 정점에 있는거다. 배웠던걸 다 적용하고 있는 거다.
password
가 일치하는지 체크를 해야한다.
이 부분을 어떻게 구현할지 정말 궁금하다. 패스워드가 어떤 모양으로 해싱되었는지
모르는데 어떻게 일치하는지 알수 있을까?
DB
를 보면 해싱된 패스워드가 있다. 전에 말했다시피 입력값이 같으면 항상 같은 해시값이
나온다고 했다. 해싱은 결정적 함수이라서 그렇다.
항상 같은 거다. 입력값이 같으면 항상 같은 출력값을 가지게 되는 거다.
그래서 DB
에 있는 패스워드를 해석할 수 없다. 유저만이 알거다.
그런데 그 패스워드의 해시값을 알고 있다. 그래서 유저가 로그인을 하려 할때
뭘 하면 되냐면 로그인할 때 유저가 입력한 패스워드를 가져다가 해싱을 하면 되는거다.
그러면 패스워드를 해싱하고 나온 해시값을 비교하면 되는거다.
그리고 만약 해시값이 서로 일치하면 유저가 정확한 패스워드를 입력했다고 알수 있다.
우선 계정을 생성하면 이 과정이 진행될거다. 패스워드를 보내면 해싱해서 값을 저장하는 거다.
그리고 로그인 할때도 패스워드를 보낼텐데 그 패스워드는 이해할수가 없다.
그렇지만 해시값이 뭔지는 알고 있다. 그리고 입력값이 바뀌지 않는 이상 출력값이 절대 바뀌지 않는다는 것도 알고 있다.
그래서 유저가 로그인 할때 보낸 패스워드를 해싱하고 비교만 해주면 된다.
해시값은 항상 같을거니까 DB
에 있는 해시값과 비교를 했을때 일치한다고 나올거다.
같은 문자열이니까 만약 로그인할때 다른 패스워드를 입력하면 DB
에 있는 해시값을 가지고 비교했을때
해시값이 많이 다를테니까 패스워드가 틀렸다고 알수 있다. 다시 말하지만 원래 패스워드가 뭔지 알 필요가 없다.
패스워드의 해시값을 알고 있으면 되는 거다. 유저가 로그인을 할때 입력한 패스워드를 해싱하고
그 값이 DB
에 있는 해시값과 같은지 비교하면 되는거다.
이 방법으로 원래 패스워드를 알지 못하더라도 패스워드가 일치하는지 알수 있다.
그래서 사용할 함수는 compare
라는 거다. 이것도 bcrypt
에 내장 되어 있다.
https://www.npmjs.com/package/bcrypt
// Load hash from your password DB.
bcrypt.compare(myPlaintextPassword, hash).then(function(result) {
// result == true
});
여기를 보고 compare()
를 쓰기만 하면 된다. myPlaintextPassword
에 들어가는 건
유저가 입력한 패스워드이고 hash
에는 DB
에 있는 해시값을 넣어주면 된다.
다시 말하면 myPlaintextPassword
에는 유저가 입력한 패스워드를 넣고
login form
에서 입력한 패스워드를 말하는거다. 이게 틀렸을지 맞았을지는 아무도 모른다.
그리고 hash
에는 DB
에 있는 패스워드를 넣을거다. 그러면 bcrypt
가 비교를 한다.
bcrypt
가 이 패스워드를 해싱하고 해시값과 비교할거다.
알다시피 입력값이 바뀌지 않으면 출력 값이 항상 같다.
여기에 나와 있다시피 result
로 true
를 받게 된다.
아니면 이렇게 할수도 있다.
async function checkUser(username, password) {
//... fetch user from a db etc.
const match = await bcrypt.compare(password, user.passwordHash);
if(match) {
//login
}
//...
}
이걸로 만들어 보도록 한다.
우선 유저가 로그인하려는 계정이 뭔지부터 알아햐 한다. 왜냐하면 로그인 form
에 입력한
패스워드인 Plain text
가 필요하고, 해시도 필요하다.
어떤 계정으로 로그인하려고 하는 건지 알아햐 한다. 그러면 user
를 먼저 찾아본다.
export const postLogin = async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user) {
return res.status(400).render("login", {
pageTitle: "Login",
errorMessage: "An account with this username does not exists.",
});
}
console.log(user.password);
res.end();
user
를 먼저 찾아보고 req.body
에서 가져온 username
을 가지는 User
를 찾을거다.
그런데 user
를 두번씩이나 찾고 있다. 한번은 user
가 존재하는지 체크하고
두번째로는 DB
에서 user
를 가져오고 있다. 한곳에서 같이 하는게 좋을것 같다.
그래서 exists
대신 user
를 가져오도록 한다. user
가 존재하지 않는건
(!user)
로 알수 있게 되었다. 이제 바로 쓸수 있는 user
가 생겼다.
그러면 user.password
를 console.log
하고 잘나오는지 확인해 본다.
콘솔을 확인해 보면 해싱된 패스워드값이 출력 되었다. DB
에서 가져온거다.
이제 bcrypt
의 compare
를 쓰기만 하면 된다. 그러면 bcrypt
를 import
한다.
import bcrypt from "bcrypt";
export const postLogin = async (req, res) => {
const { username, password } = req.body;
const pageTitle = "Login";
const user = await User.findOne({ username });
if (!user) {
return res.status(400).render("login", {
pageTitle,
errorMessage: "An account with this username does not exists.",
});
}
const ok = await bcrypt.compare(password, user.password);
if (!ok) {
return res.status(400).render("login", {
pageTitle,
errorMessage: "Wrong password",
});
}
그리고 compare
를 쓰고 function
에는 유저가 입력한 패스워드를 넣어준다.
그러면 body
에서 가져온 password
를 넣는다. 그리고 암호화 된 패스워드를 넣어준다.
그건 바로 user.password
이다. 그리고 만약에 ok
가 아닐때 위에선 사용한 에러메세지
복사해 주었다. 그리고 메세지만 다르게 설정 하였다. 이렇게 하면 패스워드를 체크하게 만든거다.
그리고 살펴보면 pageTitle
을 2번씩이나 사용하고 있는데 중복 되는 내용이니깐
변수를 만들어주었다.
const pageTitle = "Login";
이제 잘 작동하는지 테스트해본다. 틀린 패스워드를 입력하면 에러메세지가 나오고
맞는 패스워드를 작성하면 연결이 끊긴다. res.end()
를 했기 때문에 그렇다.
이게 실행되면 에러가 없었다는 거다. 그렇다는건 성공적으로 패스워드를 비교했다는거다.
이제 패스워드가 맞는지 비교 할수 가 있는거다. 원래 패스워드가 뭔지 알 필요도 없다.
postLogin controller
를 만들었다. 다음으로는 실제로 유저를 로그인 시켜야 한다.
홈 화면으로 갈수 있도록 res.redirect("/")
를 return
하고
console.log("LOG USER IN!!! COMING SOON~!");
return res.redirect("/");
};
"로그인이 되었다" 고 알려주는 console
을 만들었다. 왜냐하면 로그인했다는 말은
접속한 유저가 누구인지 기억하고 있다는 거니까 말이다.
이걸 위해서는 쿠키와 세션에 대해 얘기해야 한다. 테스트를 해본다.
오류가 나면 메세지를 띄워주고 패스워드가 맞으면 로그인이 된다. 홈화면으로 이동하고
패스워드를 저장할건지 물어본다. 왜냐하면 redirect
를 했는데 이 때 브라우저는 아무
문제가 없다고 판단한거다. 문제가 있는 상태 코드를 보내지도 않았고 말이다.
그래서 브라우저가 이게 맞는 패스워드라고 판단한거다. 아직 실제로 로그인을 하지는 않았지만 말이다.
그리고 콘솔로 가보면 잘 출력 되고 있다. "LOG USER IN!!! COMING SOON~!"
다 잘 동작되고 드디어 로그인 하는 걸 만들었다. 적어도 어떤 유저가 로그인 한건지는 알수 있지만
이제 실제로 로그인을 구현해야 한다. 그런데 이걸 위해서는 세션에 대해 알아야 한다.