게시판 - Login 기능 추가

소스코드

이 게시물에는 코드작성이 포함되어 있습니다. 소스코드를 받으신 후 진행해 주세요. MEAN Stack/개발 환경 구축에서 설명된 프로그램들(git, npm, atom editor)이 있어야 아래의 명령어들을 실행할 수 있습니다.

이 게시물의 소스코드는 게시판 만들기 / 게시판 - User Error 처리에서 이어집니다.

board.git 을 clone 한 적이 있는 경우: 터미널에서 해당 폴더로 이동 후 아래 명령어들을 붙여넣기합니다. 폴더 내 모든 코드가 이 게시물의 코드로 교체됩니다. 이를 원치 않으시면 이 방법을 선택하지 마세요.

git reset --hard
git pull
git reset --hard f17dc41
git reset --soft 3882107
npm install
atom .

board.git 을 clone 한 적이 없는 경우: 터미널에서 코드를 다운 받을 폴더로 이동한 후 아래 명령어들을 붙여넣기하여 board.git 을 clone 합니다.

git clone https://github.com/a-mean-blogger/board.git
cd board
git reset --hard f17dc41
git reset --soft 3882107
npm install
atom .

- Github에서 소스코드 보기: https://github.com/a-mean-blogger/board/tree/f17dc41dd2c38918104341c29c876a3a1cf82427


passport package를 사용해서 login 기능을 만들어 봅시다. passport는 node.js에서 user authentication(사용자 인증, 즉 login)을 만들기 위해 널리 사용되는 package입니다. passport는 단독으로 사용할 수 없고, passport strategy package와 함께 사용해야 합니다. passport package는 인증 시스템을 위한 bass package이며, passport strategy package는 구체적인 인증 방법을 구현하는 package입니다.

이렇게 package가 나눠진 이유는 passport strategy package 가 인증 방법별로 수백가지(Facebook strategy, Twitter strategy, 심지어 Naver strategy도 있습니다)가 되기 때문인데, 실제 한 프로그램에서 사용하는 strategy는 그 중에 몇 개밖에 안됩니다. 즉 프로그램에 필요한 인증 방법만 설치하기 위해서 package를 분리한 것입니다.

이번 포스팅에서는 입력받은 username, password과 DB에 존재하는 data의 값을 비교해서 login을 하는 local strategy를 사용하며 따라서 passport package와 passport-local package 두가지를 설치해 주어야 합니다.

로그인의 기본 원리

웹 프로그램에서 server와 client간의 정보교환은 단발성입니다. 사용자의 browser(client)에서 주소가 입력되거나, link가 click이 되면 server로 요청(request)이 전달되고, server는 요청에 맞는 결과를 응답(response)하게 되는 것이죠.

이렇듯 server와 client의 통신은 연결을 유지하고 있지 않기 때문에 client를 구별하기 위해서는 각각의 request에 고유한 식별코드가 필요합니다. 이 식별코드는 사이트에 처음 접속하는 순간 생성이 되어 client의 cookie에 저장이 되고, server에 요청을 할때마다 같이 server로 전달됩니다.
서버에는 이 식별코드가 session에 저장되어 어느 client로 부터 요청이 오는지를 구별할 수 있게 됩니다.

이후 로그인을 하게 되면 session에 그 상태가 기록이 되고 다음번 request부터는 로그인한 상태로 인식하게 되는 것입니다.

또한 로그인은 DB에 이미 등록되어 있는 user를 찾는 것인데, 로그인시에 DB로 부터 user를 찾아 session에 user 정보의 일부(간혹 전부)를 등록하는 것을 serialize라고 합니다. 반대로 session에 등록된 user 정보로부터 해당 user를 object로 만드는 과정을 deserialize라고 하며, server에 요청이 올때마다 deserialize를 거치게 됩니다.

폴더구조

주황색은 변경된 파일, 초록색은 추가된 파일, 회색은 변화가 없는 파일입니다.

Package 설치

passport, passport-local package들을 설치해 줍니다.

$ npm install --save passport passport-local

코드 - js

// index.js

var express    = require("express");
var mongoose   = require("mongoose");
var bodyParser  = require("body-parser");
var methodOverride = require("method-override");
var flash     = require("connect-flash");
var session    = require("express-session");
var passport   = require("./config/passport"); // 1
var app = express();

// DB setting...
// Other settings...

// Passport // 2
app.use(passport.initialize());
app.use(passport.session()); 

// Custom Middlewares // 3
app.use(function(req,res,next){
 res.locals.isAuthenticated = req.isAuthenticated();
 res.locals.currentUser = req.user;
 next();
})

// Routes...
// Port setting...

1. passport가 아닌, config/passport module를 passport 변수에 담았습니다. ./confing/passport.js는 밑에서 살펴보겠습니다. passport는 와 passport-local package는 index.js에 require되지 않고 config의 passport.js에서 require됩니다.

2. passport.initialize()는 passport를 초기화 시켜주는 함수, passport.session()는 passport를 session과 연결해 주는 함수로 둘다 반드시 필요합니다.(session은 게시판 - User Error 처리에서 설치한 express-session package로부터 생성되므로, 로그인을 구현하기 위해서는 express-session package와 session생성 코드 app.use(session({secret:"MySecret"}));가 반드시 필요합니다.)

3. app.use에 함수를 넣은 것을 middleware라고 합니다. 사실 route에 위에 // Other settings에 있는 app.use들도 middleware들이죠.
app.use에 있는 함수는 request가 올때마다 route에 상관없이 무조건 해당 함수가 실행됩니다.
위치가 중요한데, app.use들 중에 위에 있는 것 부터 순서대로 실행되기 때문이죠. route과도 마찬가지로 반드시 route 위에 위치해야 합니다.
app.use에 들어가는 함수는 route에 들어가는 함수와 동일한req,res,next의 3개의 parameter를 가집니다.
함수안에 반드시 next()를 넣어줘야 다음으로 진행이 됩니다.

req.isAuthenticated()는 passport에서 제공하는 함수로, 현재 로그인이 되어있는지 아닌지를true,false로 return합니다.
req.user는 passport에서 추가하는 항목으로 로그인이 되면 session으로 부터 user를 deserialize하여 생성됩니다.(이 과정 역시 밑에서 살펴보겠습니다.)
req.locals에 위 두가지를 담는데, req.locals에 담겨진 변수는 ejs에서 바로 사용가능합니다.
req.locals.isAuthenticated는 ejs에서 user가 로그인이 되어 있는지 아닌지를 확인하는데 사용되고, req.locals.currentUser는 로그인된 user의 정보를 불러오는데 사용됩니다.

// config/passport.js

var passport   = require("passport");
var LocalStrategy = require("passport-local").Strategy; // 1
var User     = require("../models/User");

// serialize & deserialize User // 2
passport.serializeUser(function(user, done) {
 done(null, user.id);
});
passport.deserializeUser(function(id, done) {
 User.findOne({_id:id}, function(err, user) {
  done(err, user);
 });
});

// local strategy // 3
passport.use("local-login",
 new LocalStrategy({
   usernameField : "username", // 3-1
   passwordField : "password", // 3-1
   passReqToCallback : true
  },
  function(req, username, password, done) { // 3-2
   User.findOne({username:username})
   .select({password:1})
   .exec(function(err, user) {
    if (err) return done(err);

    if (user && user.authenticate(password)){ // 3-3
     return done(null, user);
    } else {
     req.flash("username", username);
     req.flash("errors", {login:"Incorrect username or password"});
     return done(null, false);
    }
   });
  }
 )
);

module.exports = passport;

1. strategy들은 거의 대부분이 require다음에 .Strategy가 붙습니다. .Strategy없이 사용해도 되는 것도 있는데, 다들 붙여주니까 같이 붙여줍시다.
꼭 붙여야 된다거나, 혹은 다른 단어가 붙는 경우도 있는데, 이런건 https://www.npmjs.com/에서 해당 package를 검색한 후 해당 package의 공식 문서에서 확인할 수 있습니다.

2. passport.serializeUser는 login시에 DB에서 발견한 user를 어떻게 session에 저장할지를 정하는 부분입니다. user정보 전체를 session에 저장할 수도 있지만, session에 저장되는 정보가 너무 많아지면 사이트의 성능이 떨어질 수 있고, user object가 변경되면 변경된 부분이 반영되지 못하므로 user의 id만 session에 저장합니다.

passport.deserializeUser는 request시에 session에서 어떻게 user object를 만들지를 정하는 부분입니다. 매번 request마다 user정보를 db에서 새로 읽어오는데, user가 변경되면 바로 변경된 정보가 반영되는 장점이 있습니다. 다만 매번 request마다 db를 읽게 되는 단점이 있는데.. 선택은 그때 그때 상황에 맞게 하시면 됩니다.

3. local strategy를 설정하는 부분입니다.

3-1. 만약 로그인 form의 username과 password항목의 이름이 다르다면 여기에서 값을 변경해 주면 됩니다. 사실 이 코드에서는 해당 항목 이름이 form과 일치하기 때문에 굳이 쓰지 않아도 됩니다. 예를들어 로그인 form의 항목이름이 email, pass라면 usernameField : "email", passwordField : "pass"로 해야 합니다.

3-2. 로그인 시에 이 함수가 호출됩니다. DB에서 해당 user를 찾고, user model에 설정했던 user.authenticate 함수를 사용해서 입력받은 password와 저장된 password hash를 비교해서 값이 일치하면 해당 user를 done에 담아서 return하고 (return done(null, user);), 그렇지 않은 경우 username flash와 에러 flash를 생성한 후 done에 false를 담아 return합니다.(return done(null, false);) user가 전달되지 않으면 local-strategy는 실패(failure)로 간주됩니다.

3-3. user.authenticate(password)는 입력받은 password와 db에서 읽어온 해당 user의 password hash를 비교하는 함수로 게시판 - 계정 비밀번호 암호화(bcrypt) 강의에서 bcrypt로 만든 함수입니다.

참고: done함수의 첫번째 parameter는 항상 error를 담기 위한 것으로 error가 없다면null을 담습니다.

// routes/home.js

var express = require("express");
var router = express.Router();
var passport= require("../config/passport"); // 1

// Home ...

// Login // 2
router.get("/login", function (req,res) {
 var username = req.flash("username")[0];
 var errors = req.flash("errors")[0] || {};
 res.render("home/login", {
  username:username,
  errors:errors
 });
});

// Post Login // 3
router.post("/login",
 function(req,res,next){
  var errors = {};
  var isValid = true;
  if(!req.body.username){
   isValid = false;
   errors.username = "Username is required!";
  }
  if(!req.body.password){
   isValid = false;
   errors.password = "Password is required!";
  }

  if(isValid){
   next();
  } else {
   req.flash("errors",errors);
   res.redirect("/login");
  }
 },
 passport.authenticate("local-login", {
  successRedirect : "/",
  failureRedirect : "/login"
 }
));

// Logout // 4
router.get("/logout", function(req, res) {
 req.logout();
 res.redirect("/");
});

module.exports = router;

1. index.js와 마찬가지로 passport가 아닌 config/passport를 passport 변수에 담았습니다(상대주소라서 여긴 점이 두개입니다.). 사실 두군대 중에 한군대만 config/passport를 require해 주면 되는데, 저는 그냥 passport는 무조건 config에서 가져오는 걸로 했습니다.

2. login view를 보여주는 route입니다.

3. login form에서 보내진 post request를 처리해 주는 route입니다. 두개의 callback이 있는데, 첫번째 callback은 보내진 form의 validation을 위한 것으로 에러가 있으면 flash를 만들고 login view로 redirect합니다. 두번째 callback은 passport local strategy를 호출해서 authentication(로그인)을 진행합니다.

4. logout을 해주는 route입니다. passport에서 제공된 req.logout 함수를 사용하여 로그아웃하고 "/"로 redirect합니다.

코드 - ejs

<!-- views/home/login.ejs -->

<!DOCTYPE html>
<html>
 <head>
  <% include ../partials/head %>
 </head>
 <body>
  <% include ../partials/nav %>

  <div class="container home home-login">

   <form class="login-form form-horizontal" action="/login" method="post">
    <div class="contentBox">
     <h3 class="contentBoxTop">Login</h3>
     <fieldset>
      <div class="form-group <%= (errors.username)?'has-error':'' %>">
       <label for="username" class="col-sm-3 control-label">Username</label>
       <div class="col-sm-9">
        <input class="form-control" type="text" id="username" name="username" value="<%= username %>">
        <% if(errors.username){ %>
         <span class="help-block"><%= errors.username %></span>
        <% } %>
       </div>
      </div>
      <div class="form-group <%= (errors.password)?'has-error':'' %>">
       <label for="password" class="col-sm-3 control-label">Password</label>
       <div class="col-sm-9">
        <input class="form-control" type="password" id="password" name="password" value="">
        <% if(errors.password){ %>
         <span class="help-block"><%= errors.password %></span>
        <% } %>
       </div>
      </div>
      <% if(errors.login){ %>
       <div class="has-error">
        <span class="help-block"><%= errors.login %></span>
       </div>
      <% } %>
     </fieldset>
    </div>
    <div class="buttons">
     <input class="btn btn-default" type="submit" value="Submit">
    </div>
   </form>

  </div> <!-- container end -->
 </body>
</html>

login view입니다. user form과 형태가 거의 유사합니다.

<!-- views/partials/nav.ejs -->

<nav class="navbar navbar-default">
 <div class="container">
  <!-- ... -->
  <div class="collapse navbar-collapse" id="myNavbar">
   <!-- ... -->
   <ul class="nav navbar-nav navbar-right">
    <% if(isAuthenticated){ %>
     <li><a href="/users/<%= currentUser.username %>">My Account</a></li>
     <li><a href="/logout">Logout</a></li>
    <% } else { %>
     <li><a href="/users/new">Sign Up</a></li>
     <li><a href="/login">Login</a></li>
    <% } %>
   </ul>
  </div>
 </div>
</nav>

nav-bar가 login을 한 경우(if(isAuthenticated))에는 My Account 메뉴와 Logout 메뉴을 보여주고, login을 하지 않은 경우(else)에는 Sign Up 메뉴와 Login 메뉴를 보여줍니다.

코드 - css

/* public/css/master.css */

/* global style ...*/

/* home style */
/* ... */
.home-login{ /* 1 */
 max-width: 330px;
 font-family: 'Open Sans', sans-serif;
 font-size: 12px;
}

/* post style ... */
/* user style ... */

1. home-login의 기본 스타일이 추가되었습니다.

실행결과

로그인 전의 화면입니다.

로그인전에는 우측상단에 Sign Up과 Login 메뉴가 보입니다.

로그인을 하고 나면 우측 상단에 My Account와 Logout 메뉴가 보입니다.

마치며

이번 포스팅에서 꼭 알고 넘어가야 하는 부분은 실제 로그인이 일어날때 코드의 진행순서입니다.

  1. 로그인 버튼이 클릭되면 routes/home.js의 post /login route의 코드가 실행됩니다.
  2. 다음으로 config/passport.js의 local-strategy의 코드가 실행됩니다.
  3. 로그인이 성공하면 config/passport.js의 serialize코드가 실행됩니다.
  4. 마지막으로 routes/home.js의 post /login route의 successRedirect의 route으로 redirect가 됩니다.
  5. 로그인이 된 이후에는 모든 신호가 config/passport.js의 deserialize코드를 거치게 됩니다.

이번 포스팅에서 로그인을 구현했지만, 실제로는 로그인하기 전과 로그인한 후의 차이는 메뉴가 바뀌는 것 밖에 없습니다. 게시판에 글을 등록해도 로그인한 유저로 글을 등록하지 않으며, 다른 사람의 계정의 정보를 수정할 수도 있죠.
다음 포스팅부터는 이러한 기능들을 유저별로 제한 하는 방법에 대해 알아보겠습니다.
긴 글 읽으시느라 수고하셨습니다!

댓글

P
Paul Choi 2017.02.14
안녕하세요 댓글을 많이 달진 않았지만 덕분에 Node.js를 많이 배우고 있습니다. :) 저 질문이 하나 있습니다. login을 할때 만약 login이 pass가 되면 passport.authenticate 안에 successRedirect:에 있는 루트로 가게 되는데.. 거기서 그 루트에 로그인 id가 들어가게 하고 싶은데.. 어떻게 하면 좋을까요? 예를 들어 로그인 아이디가 ghd이고 pass를 했다면   localhost:4000/?channel=ghd 이렇게 하고 싶은데... 제가 생각했던 방법은 var data_1 = {email:''}; 라고 initialize 해준 후  app.post function 안에 data_1.email = req.body.email; 이라고 해주고 successRedirect: ('/?channel=' + data_1.email) 이라고 했는데 안되더라구요.. 좋은 방법이 없을까요...?
박수진 2017.11.05
안녕하세요 강의 진행 중 에러가 발생했는데요 TypeError: passport.initialize is not a function 이런식으로 initialize가 함수가 아니라는 에러가 뜨는데요 index.js에서 app.initialize(); 이부분에 에러가 생기는 것 같아요 require도 똑같이 하고 경로도 제대로 설정했는데 당혹스럽네요.. stackoverflow에도 에러원인이 정확히 나오질 않아서 도움요청합니다 ㅠㅠ 혹시 시간되시면 https://github.com/Hajimara/mean 죄송스럽지만 피드백 부탁드립니다..
I
Ian H 2017.11.07
@박수진,
바로 정답을 알려드리면 실력이 안느니까 힌트를 드릴게요.
is not function error는 말 그대로 해당 함수를 찾을 수 없어서 납니다. 현재 passport 오브젝트의 initialize라는 함수를 찾을 수 없는것이죠.
passport.initialize를 호출하는 index.js의 40번째 줄 바로 위에 console.log("passport: ", passport);를 넣어서 passport가 어떤 오브젝트인지 살펴보세요.
passport:  {}
이라고 나오죠.
index.js 7번째 줄에서 분명히 var passport       = require("./config/passport");로 선언이 되었는데 빈 오브젝트가 표시되는 이유는 뭘까요?!
박수진 2017.11.07
@Ian H,
require시에 사용될때 필요한 module.exports를 빠트렸네요! 나머지 잔 오류도 고쳤습니다. 손으로 직접 쳐서 쓰다보니 잔고장이 많이나네요  알려주셔서 감사합니다
H
Hyunsung Kim 2017.11.22
로그인 기능을 추가하고 나서 동일하게 테스트를 아이디 4개 정도 만들면서 해봤는데 계속 아이디, 비밀번호가 틀립니다. DB 뜯어보는 방법도 잘 모르고 해서... ㅜㅜ 막혔네요 어찌해야 할까요. 프롬프트 창에는 node:15024 mpromise 에러만 뜨고 나머지는 정상 작동 중이라고 뜨는데 어찌해야 할 지 모르겠네요
I
Ian H 2017.11.22
@Hyunsung Kim,
소스코드를 github에 올리고 주소 알려주시면 제가 한번 살펴보겠습니다
H
Hyunsung Kim 2017.11.22
@Ian H,
역시나 빠른 댓글 감사합니다 ㅜㅜ https://github.com/kokily/guestbook 이곳입니다. 아직 작성중이지만 login 기능 추가 전까진 정상적으로 nodemon 작동되었었습니다~!
I
Ian H 2017.11.22
@Hyunsung Kim,
로그인창에 username, password를 입력하고 submit을 하면 서버가 다운되네요. router.post("/login",...) 부분을 살펴보면 
passport.authenticate("local-login", {     successRedirect: "/",     failureRedirect: "/login"   }) ) 이 전까지는 정상적으로 작동하죠, 이 다음으로는 config/passport.js의 
passport.use("local-login",   new LocalStrategy({     usernameField: "username",     passwordField: "password",     passReqToCallback: true   },   function(req, username, password, done) {     User.findOne({username: username}).select({password: 1}).exec(function(err, user) {       if(err) return done(err);
      if(user && user.authenticate(password)) {         return done(null, user);       } else {         req.flash("username", username);         req.flash("errors", {login: "아이디, 비밀번호가 틀립니다."});         return done(null, false);       }     });   }) );
에서 
function(req, username, password, done) { ... }
요기로 이동합니다. 
여기 안에 console.log를 찍어보면서 에러가 정확히 어디서 발생하는지, 왜 발생하는지 한번 찾아보세요. 정 못찾으시면 다시 어디까지 찾으셨는지 답글로 알려주세요^^
H
Hyunsung Kim 2017.11.24
@Ian H,
음.... 제가 저~~~~엉말 초보라.... ㅜㅜ console.log(X)  를 여기저기 넣어보고 있는데 X안에 무엇을 넣어서 검사해야 하는 걸까요? 어제부터 이틀내내 고민중인데 어떻게 찾아야 할 지 모르겠네요.
I
Ian H 2017.11.24
@Hyunsung Kim,
해당 위치에서 확인할 수 있는 값을 넣으셔도 되고 아니면 console.log("hi")를 넣어서 코드가 그 줄까지 진행되는지 확인할 수 있습니다. nodemon을 실행시킨 콘솔에 hi가 찍혀나오면 해당 위치까지 코드가 진행되었다는 뜻이지요.
H
Hyunsung Kim 2017.11.29
@Ian H,
음... 처음부터 다시 해보고 있었는데.. 오타가 있었네요 찾았습니다. 감사합니다~!
I
Ian H 2017.11.30
@Hyunsung Kim,
시간이 오래걸려 걱정했었는데 찾으셨다니 다행입니다^^
H
Hyunsung Kim 2017.12.21
또~ 질문입니다. 하하...;; User.js 스키마에서 name 부분의 match에 /^.{4,12}$/ 이렇게 했는데 이름에 제 이름인 "김현성"을 넣었더니 이름은 4-12글자.... 에러메세지가 나옵니다. 영어로 입력시에는 잘 되는데 한글 이름 치면 안되네요 ㅜㅜ
I
Ian H 2017.12.21
@Hyunsung Kim,
'김현성'은 세글자에요~~ /^.{4,12}$/는 4글자~12글자를 받을 수 있는 정규표현식입니다~
쿠아 2018.03.24
안녕하세요 포스팅 글로 많은 도움 받고 있습니다.
다름이 아니라 다음의 코드에서 커스텀 미들웨어를 사용하는데,
제가 이해한 바로는 ejs 파일에서 해당 메소드명을 호출하면 마치 콜백으로 값을 반환해주는게 맞는지 궁금합니다.
// Custom Middlewares // 3 app.use(function(req,res,next){  res.locals.isAuthenticated = req.isAuthenticated();  res.locals.currentUser = req.user;  next(); })
isAuthenticated 
즉 
req.isAuthenticated();  이 친구가 되는건가요?;;
질문이 두서가 너무 없어서 죄송합니다.
I
Ian H 2018.03.26
@쿠아,
안녕하세요 맞습니다. res.locals에 들어가는 모든 오브젝트는 ejs에서 바로 호출할 수 있습니다. 
쿠아 2018.04.06
안녕하세요 다시 한번 질문하게 됬네요 ^^;;  
코드를 다음과 같이 설정하였는데 생각보다 잘 작동하지 않네요 코드의 이해도가 아직 부족해서 그런것 같은데 이유를 알 수 있을까요. 
제가 의도한 바는 접속 시 마다 특정 데이터를 보내주고 싶습니다.(동적 네비게이션)
https://jsfiddle.net/7wncgag2/
I
Ian H 2018.04.06
@쿠아,
위 미들웨어를 실행하면 console.log('커스텀 미들웨어 체킹' ... 이 먼저 출력되고 console.log('메뉴 변수... 가 나중에 출력이 될 거에요. schema.carrierModel.find( ... 이 비동기 함수라서 그렇습니다. 
app.use(function(req, res, next){     schema.carrierModel.find({}, function(err, result){         res.locals.isAuth = req.isAuthenticated();         res.locals.curUser = req.user;         res.locals.nav = result);         next();     }); });
이렇게 해보세요
쿠아 2018.04.06
@Ian H,
ian H 님 답변 감사합니다. 
말씀하신 코드대로 작성하니 정상작동합니다. 
다만 코드를 함수화 시키고 깔끔하게 작성하고 싶었는데 역시 해당 방법밖에 없을려나요 ^^;;
I
Ian H 2018.04.06
@쿠아,
// Navigation middle ware app.use(function(req, res, next){     schema.carrierModel.find({}, function(err, result){         res.locals.nav = result;         next();     }); });
// Auth middle ware app.use(function(req, res, next){     schema.carrierModel.find({}, function(err, result){         res.locals.isAuth = req.isAuthenticated();         res.locals.curUser = req.user;         next();     }); });
요렇게 nav만 처리해주는 미들웨어로 분리해주면 좀 더 깔끔해지죠. 아예 미들웨어들만 가지고 있는 js파일을 만든 후에 거기에 함수들을 넣어놓고 메인js에서는 해당 함수들 호출만 해주어도 됩니다.
app.use(middlewares.getNavigation); app.use(middlewares.authentication);
이렇게 되겠죠
손진아 2018.05.10
안녕하세요 주어진 자료 정말 감사히 잘 공부하고 있습니다. 다름이 아니라 이번 강의를 다 코드를 써본후 실행을 하면
ReferenceError: C:\Users\JINAHSON\Desktop\JINA_HP\views\layouts\layout.ejs:38     36|       <!--user section-->     37|       <form class="navbar-form navbar-right">  >> 38|        <% if(isAuthenticated){ %>     39|          <a href="/users/<%= currentUser.username %>">My Account</a>     40|          <a href="/logout">Logout</a>     41|        <% } else { %>
isAuthenticated is not defined
이런 에러가 뜹니다. 
아마 커스텀 미들웨어의  // Custom Middlewares // 3 app.use(function(req,res,next){     res.locals.isAuthenticated = req.isAuthenticated();     console.log(isAuthenticated);      res.locals.currentUser = req.user;     next(); }); 
이부분을 인식하지 못하는거 같은데요 어떻게 수정할수 있는 방법이 있을까요?
I
Ian H 2018.05.14
@손진아,
안녕하세요! 코드 작성중에 오류가 있는 듯 합니다. 작성하신 코드를 github에 올리시고 주소를 알려주시면 제가 한번 디버깅해 보겠습니다.
G
GS 2018.09.12
자꾸 질문만 많아지는 것 같아 죄송합니다..많이 초보라서요ㅠㅠ passport에서 deserializeUser를 사용할 때 User.findOne에 변수에서는 {_id:id}라고 쓰이고 local strategy에서 function안에 User.findOne에서는 ({username:username})로 쓰이고 있는데 id에 언더바를 붙인것과 안붙이는 것에 대한 차이점이 뭔지 궁금합니다
I
Ian H 2018.09.12
@GS,
디테일 한 것들에 대한 궁금증을 그냥 지나치지 않는 것은 배움에 있어서 중요하다고 생각합니다!
mongodb에서 모든 데이터(document)들은 자동으로 unique id를 가지게 되는데, 이것이 저장되는 항목 이름이 _id입니다. User의 Schema를 보면, username, password, name, email의 항목을 설정했고, _id는 설정한 적이 없죠.
passport.deserializeUser(function(id, done) {  User.findOne({_id:id}, function(err, user) {   done(err, user);  }); });
에서 findOne부분에 {_id:id}로 쓰인 이유는 함수의 parameter 이름이 _id가 아니라 id로 했기 때문입니다. _id로 바꾸시려면 parameter부분과 함께 _id로 바꾸시면 됩니다.
passport.deserializeUser(function(_id, done) { <- 여기와  User.findOne({_id:_id}, function(err, user) {     <- 여기를 함께 바꾸시면 됩니다.   done(err, user);  }); });
G
GS 2018.09.13
@Ian H,
아 id가 mongodb에서 가져오는거군요!! 감사합니다 덕분에 따라하면서 열심히 배우고있어요. 근데 아직 기본이 많이 부족한거같아서 node 책을 구매했습니다. 책보고 다시 공부하고 한번더 복습하러 오겠습니다! 좋은 게시물 만들어주셔서 감사해요
I
Ian H 2018.09.13
@GS,
화이팅!
칠동이 2019.02.18
(node:12996) DeprecationWarning: collection.ensureIndex is deprecated. Use createIndexes instead. (node:12996) DeprecationWarning: collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead.
이런 에러가 떳어요 무슨말일까요
칠동이 2019.02.18
근데 되기는 되용
I
Ian H 2019.02.18
@칠동이,
ensureIndex 대신에 createIndexes를, findAndModify 대신에 findOneAndReplace를 사용하라는 뜻입니다.  Warning이므로 당장 하지 않아도 됩니다. 아마 버전업이 계속되다 보면 언젠간 warning이 error로 바뀌면서 더이상 되지 않는 때가 올 거에요.
이서영 2019.05.14
시리얼라이즈 설명을 하셨을때, 세션의 무게를 줄이기 위해서 id값을 대표로 저장한다 하셨는데요 이것이 Rest 에서 설명하는 대푯값 상태저장에 해당하는 내용으로 봐도 될까요?
I
Ian H 2019.05.14
@이서영,
사용자 수가 늘어나게 되면 서버를 늘려야 하는데, 위와같이 서버의 세션에 정보를 저장하는 경우 세션의 공유를 위해 세션 서버를 따로 두게 됩니다. 이때 세션에 관련된 문제점이 발생하면, 코드에 문제가 있는지, 세션서버에 문제가 있는지 디버깅하기가 힘들고, 사용자 수가 폭팔적으로 늘어나면 세션서버를 관리하기가 힘듭니다. 그래서 일반적으로 세션에는 꼭 필요한 정보만 저장해서 세션을 가볍게 합니다.
REST에서는 아예 세션을 없에 버린거죠. 
REST에서 대표값 상태는.. student/:id에서 id가 데이터를 선택하는 대표값이며, 상태라는 것은 그냥 데이터로 생각하시면 됩니다. 세션과는 약간 다른 개념입니다.
댓글쓰기

이 글에 댓글을 다시려면 SNS 계정으로 로그인하세요. 자세히 알아보기

UP