게시글

5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭
강좌10 - NodeJS - 8년 전 등록 / 6년 전 수정

Passport로 회원가입 및 로그인하기

안녕하세요. 이번 시간에는 Passport.js 패키지를 사용해 회원가입과 로그인을 구현하겠습니다! 이 강좌는 ReactJS 리덕스 강좌와도 이어집니다. 

npm install passport passport-local express-session

일단 두 패키지를 설치합니다. passport는 한글로 여권입니다. 이름처럼 자신의 웹사이트에 방문할 때 여권같은 역할을 합니다. 로그인을 쉽게 할 수 있게 도와줍니다. 이름 잘 지었죠? passport-local은 로그인을 직접 구현할 때 사용됩니다. 이외에 passport-google-oauth, passport-facebook, passport-twitter, passport-kakao, passport-naver 같이 SNS 계정을 통해서 바로 로그인할 수 있는 패키지도 있습니다. 제 블로그의 로그인 창을 보면 위의 패키지들을 사용한 것을 알 수 있습니다.

express-session은 passport로 로그인 후 유저 정보를 세션에 저장하기 위해 사용합니다.

이렇게 설치했으니 passport를 사용해보겠습니다. 이게 우리나라에 들어올 수 있게 해주는 여권입니다.

passport.js

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const Users = require('./user');

module.exports = () => {
  passport.serializeUser((user, done) => { // Strategy 성공 시 호출됨
    done(null, user); // 여기의 user가 deserializeUser의 첫 번째 매개변수로 이동
  });

  passport.deserializeUser((user, done) => { // 매개변수 user는 serializeUser의 done의 인자 user를 받은 것
    done(null, user); // 여기의 user가 req.user가 됨
  });

  passport.use(new LocalStrategy({ // local 전략을 세움
    usernameField: 'id',
    passwordField: 'pw',
    session: true, // 세션에 저장 여부
    passReqToCallback: false,
  }, (id, password, done) => {
    Users.findOne({ id: id }, (findError, user) => {
      if (findError) return done(findError); // 서버 에러 처리
      if (!user) return done(null, false, { message: '존재하지 않는 아이디입니다' }); // 임의 에러 처리
      return user.comparePassword(password, (passError, isMatch) => {
        if (isMatch) {
          return done(null, user); // 검증 성공
        }
        return done(null, false, { message: '비밀번호가 틀렸습니다' }); // 임의 에러 처리
      });
    });
  }));
};

passport는 독특하게 Strategy(전략)이라는 것을 사용합니다. 모든 passport의 플러그인들은 사용하려면 전략을 짜 주어야 합니다. 위의 경우는 local 로그인의 경우의 전략입니다. 일단 serializeUser과 deserializeUser은 잠시 뒤에 설명할 거고요. LocalStrategy 부분을 보시죠.

usernameFieldpasswordField는 어떤 폼 필드로부터 아이디와 비밀번호를 전달받을 지 설정하는 옵션입니다. 위의 경우는 body에 데이터가 { id: 'zerocho', pw: 'pswd' } 이렇게 오면 뒤의 콜백 함수의 id 값이 zerocho, password 값이 pswd가 됩니다. session은 말 그대로 세션을 사용할 지 안 할 지를 선택합니다. passReqToCallback은 true로 해두면 뒤의 콜백이

(req, id, password, done) => {};

으로 바뀝니다. id 매개변수 앞에 req 매개변수가 추가되었습니다. req를 통해서 express의 req 객체에 접근할 수 있습니다. 저는 보통 passReqToCallback을 true로 해 두어 req 객체를 passport 인증 시 활용합니다.

아이디와 비밀번호 값이 들어오면 뒤의 콜백 함수가 실행되는데, 내용을 보면 id로 유저를 찾은 후, 유저가 없으면 존재하지 않는 아이디라고 에러를 보냅니다. 유저가 있다면, 이제 비밀번호를 비교해서(비밀번호를 비교하는 부분은 아래 user.js 파일의 comparePassword를 참조하세요), 비교 과정에서 서버 에러가 나면 done(findError)로 에러를 리턴하고, 비밀번호가 맞을 경우 done(null, user);로 user 객체를 전송해주고, 틀렸을 경우는 비밀번호가 틀렸다고 done(null, false, { message: '에러메시지' })로 메시지를 전송합니다.

done이 인자를 세 개나 받아 헷갈릴 수도 있는데 다음과 같습니다. 첫 번째 인자는 DB조회 같은 때 발생하는 서버 에러를 넣는 곳입니다. 무조건 실패하는 경우에만 사용합니다. 두 번째 인자는 성공했을 때 return할 값을 넣는 곳이고요. 성공했으면 당연히 첫 번째 인자는 null이어야겠죠? 에러가 있으면 안 되니까요. 세 번째 인자는 언제 사용하나면, 사용자가 임의로 실패를 만들고 싶을 때 사용합니다. 첫 번째 인자를 사용하는 경우는 서버에서 에러가 났을 때 무조건 실패하는 경우라고 했죠. 세 번째 인자는 위에서 비밀번호가 틀렸다는 에러를 표현하고 싶을 때 사용하면 됩니다. 이것은 서버 에러도 아니고, 사용자가 임의로 만드는 에러이기 때문에, 직접 에러 메시지도 써주는 겁니다.

다음은 많은 분들이 헷갈려하는 serializeUser와 deserializeUser입니다.

serializeUser은 방금 전에 로그인 성공 시 실행되는 done(null, user);에서 user 객체를 전달받아 세션(정확히는 req.session.passport.user)에 저장합니다. 세션이 있어야 페이지 이동 시에도 로그인 정보가 유지될 수 있습니다. deserializeUser은 실제 서버로 들어오는 요청마다 세션 정보(serializeUser에서 저장됨)를 실제 DB의 데이터와
비교합니다. 해당하는 유저 정보가 있으면 done의 두 번째 인자를 req.user에 저장하고, 요청을 처리할 때 유저의 정보를 req.user를 통해서 넘겨줍니다. 하지만 위의 예시에서는 그냥 아무런 처리과정 없이 (물론 세션에는 저장이 됩니다) 그냥 넘겨주도록 했습니다.

serializeUser에서 done으로 넘겨주는 user가 deserializeUser의 첫 번째 매개변수로 전달되기 때문에 둘의 타입이 항상 일치해야 합니다. 만약 serializeUser에서 id만 넘겨줬다면 deserializeUser의 첫 번째 매개변수도 id를 받아야 하고요. id만 있으면 그 자체로는 req.user을 만들 수 없기 때문에 User.findById(id) 메소드로 완전한 user 객체를 만들어서 done을 해주면 됩니다. 아래와 같이 주로 사용합니다.

passport.serializeUser((user, done) => { // Strategy 성공 시 호출됨
  done(null, user._id); // 여기의 user._id가 req.session.passport.user에 저장
});
passport.deserializeUser((id, done) => { // 매개변수 id는 req.session.passport.user에 저장된 값
  User.findById(id, (err, user) => {
    done(null, user); // 여기의 user가 req.user가 됨
  });
});

이 두 메소드는 꼭 있어야 passport가 작동합니다. 반드시 넣어주셔야 합니다.

user.js

const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
  id: String,
  password: String
});

userSchema.methods.comparePassword = function(inputPassword, cb) {
  if (inputPassword === this.password) {
    cb(null, true);
  } else {
    cb('error');
  }
};

module.exports = mongoose.model('users', userSchema, 'users');

몽구스를 사용하여 스키마를 만들고 비밀번호 비교 메소드 comparePassword도 만들었습니다. 현재 코드에는 비밀번호를 평문 그대로 저장하고, 비교할 때도 평문끼리 비교하고 있는데, 실제 배포 단계에서는 절대로 이래서는 안 됩니다. 이것에 관한 강좌로 암호화 강좌 가 있습니다. 서버에는 passport를 모듈을 불러와 사용할 수 있도록 코드를 추가해줍니다.

server.js

const express = require('express');
const path = require('path');
const session = require('express-session'); // 세션 설정
const db = require('./db.js');
const route = require('./route.js');
const passport = require('passport'); // 여기와
const passportConfig = require('./passport'); // 여기
const app = express();
app.set('view engine', 'pug');
app.set('views', path.join(__dirname, 'html'));
app.use(session({ secret: '비밀코드', resave: true, saveUninitialized: false })); // 세션 활성화
app.use(passport.initialize()); // passport 구동
app.use(passport.session()); // 세션 연결
db();
passportConfig(); // 이 부분 추가
app.use(express.static(path.join(__dirname, 'html')));
app.use('/', route);
app.listen(8080, () => {
  console.log('Express App on port 8080!');
});

가운데 session을 설정하는 session(), passport.initialize(), passport.session() 이 부분을 잊지 말고 넣어주셔야 합니다. 마지막으로 route.js에 로그인을 처리하는 라우트를 추가해줍니다.

route.js

const passport = require('passport');
...
router.post('/login', passport.authenticate('local', {
  failureRedirect: '/'
}), (req, res) => {
  res.redirect('/');
});

이제 /login으로 post 요청을 보내면(로그인 html은 직접 만들어 보세요. action이 '/login'이고 method가 'post', 각각의 input name이 id와 pw인 form을 만들면 됩니다) passport에서 local에 대한 인증 작업을 시작합니다. LocalStrategy를 사용해서요. 실패할 경우와 성공할 경우 어디로 돌려보낼 지 각각 failureRedirect와 res.redirect에 위치를 지정할 수 있습니다.

후, 이제 로컬 로그인이 끝났네요. 다음 시간에는 passport로 facebook 로그인을 만드는 것을 알아볼까요? 로컬 로그인보다는 간단합니다.

조회수:
0
목록
투표로 게시글에 관해 피드백을 해주시면 게시글 수정 시 반영됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright 2016- . 무단 전재 및 재배포 금지. 출처 표기 시 인용 가능.
5만명이 선택한 평균 별점 4.9의 제로초 프로그래밍 강좌! 로드맵만 따라오면 됩니다! 클릭

댓글

33개의 댓글이 있습니다.
3년 전
뀨뀨
3년 전
안녕하세요 좋은 강좌 너무 감사합니다
새해 하시는 일마다 행운 가득하시기를 기원합니다

두가지만 여쭙습니다

먼저
passport 정책 설정에서 fail 이 되었을때 message 설정을 하셨습니다
이 메시지를 다른 곳에서 (예를 들어 router)에서 사용하여
적절한 메시지를 클라이언트에게 보낼수 있는 방법이 있을까요


두번째는 Strategy callback 함수에서 문제입니다
다음 코드에서

Users.findOne({ id: id }, (findError, user) => {
if (findError) return done(findError); // 서버 에러 처리 --
if (!user) return done(null, false, { message: '존재하지 않는 아이디입니다' }); // 임의 에러 처리
return user.comparePassword(password, (passError, isMatch) => {
if (isMatch) {
return done(null, user); // 검증 성공
}
return done(null, false, { message: '비밀번호가 틀렸습니다' }); // 임의 에러 처리
});
});


각각 조건문에 의해 return done() 를 실행하고 있습니다.
그런데
조건문에 의해 done() 이 실행됬음에도 불구하고
최종 return done(null, false, { message :'비밀번호가 틀렸습니다; }) 부분이
실행이 되는 현상이 있습니다.

질문만 드리고 갑니다
3년 전
https://github.com/ZeroCho/nodejs-book/blob/master/ch9/9.5/nodebird/routes/auth.js#L30-L45
이런 식으로 좀 더 세세한 컨트롤이 됩니다. strategy에서 done에 넣었던 것이 req.login의 (authError, user, info)와 연결됩니다. 여기서 info에 적혀있는 메시지에 따라 다른 메시지 적으시면 됩니다. 마지막 질문은 return이 앞에 붙어있어서 마지막 것이 실행될 수가 없습니다. 디버거나 콘솔로그 군데군데 넣어서 확인해보세요.
4년 전
노드버드 강의 듣다가 이걸로 궁금증 해결하고 가네요ㅎㅎ
4년 전
serializeUser 함수의 자세한 기능을 알아보려고 passport문서도 봣는데.. 선생님 설명만큼 자세하진않은데 어떻게 그렇게 자세하게 알 수 있는건가요... ㅠㅠ
4년 전
saveUninitialized 값이 true인 상태에서 동작하게 하려면 어떻게 해야하나요? 예제나 node.js 교과서에서 전부 false로 하셔서요.....
4년 전
한때 React관련하여 제로초님의 영상을 보고 공부했던 한 사람입니다.
덕분이 언어 거부감에 대해 편안해져서 다시한번 감사의 말씀 드립니다.
다름이 아니고 질문이 있는데요!
로그인 처리에 앞서, 브라우저 종료시에 로그아웃되도록 어떻게 하셨는지 궁금합니다!!!
강의를 따라가다보니 SSR 처리 이후에 쿠키 정보로 매번 로그인과 유저 정보를 확인하여
불러와서 처리가 될텐데,
브라우저 종료와 구분이 어떻게 되어 종료 후엔 남지 않아 로그아웃이 되는지 궁금합니다.
(강의 말고 현재 제로초님 사이트...^^..)
저 역시 자동 로그인 기능을 만들고 싶어서 이렇게 질문을 드리고자 하는데..
자동 로그인 체크 여부를 통해
passport사용에 있어서 어떤 설정을 해주어야 하는지 감이 안옵니다..
그렇다고 express 세션 설정이 별도로 필요한지도...잘 모르겠습니다..ㅠㅠ
자동 로그인 만들기 이전에 가장 먼저 필요한 것이,
브라우저 종료시, 로그인 정보가 날라가는 것이라 생각하는데..
저같은 경우는 MysqlStore 로 세션정보를 남겨놨는데..
종료시 로그아웃 처리 조차도 힘드네요..ㅠㅠ

관련되서 의견좀 부탁드립니다..ㅠㅠ
4년 전
쿠키는 기본적으로 세션쿠키라서 브라우저 종료 시 사라집니다. 브라우저가 종료되면 로그인도 같이 풀리는 것이죠.
4년 전
제가 흐름이 잘 이해가 안돼서 질문 드립니다.
클라이언트에서 로그인 요청이 들어왔을때 처음으로 진입하는 곳은 route.js 의 /login 라우트(post) 이고, passport.authenticate 시에 passport.js 의 localStrategy 부분에 진입하는 건가요? serializeUser 는 strategy 성공 시에 내부적으로 자동으로 호출되는 것 같은데 strategy 성공 시라는 말이 localStrategy 콜백안의 내용이 모두 정상적으로 진행이 돼서 done(null, user); 에 진입해 serializeUser 와 deserializeUser 가 순차적으로 실행되는건지 이해가 잘 되지않습니다...
제가 부족한 탓에 이해를 잘 하지 못해서 이렇게 질문 드립니다.
4년 전
/login -> localStrategy로 가고요. serializeUser는 req.login 시 호출됩니다. 전략 성공은 done이 되어 passport.authenticate의 콜백이 호출되는 상황을 말합니다. deserializeUser는 serializeUser 다음에 실행되는 게 아니라 로그인 성공 후 그다음 요청부터 요청 직전에 실행되어 req에 req.user를 넣어줍니다.
4년 전
답변 감사드립니다! 덕분에 조금 이해가 되는것 같습니다.
하나 의문인 것이.. serializeUser 와 deserializeUser 이둘의 역할이 크게 차이가 나나요? 굳이 이 둘이 나뉘어져 있는 이유가 있는것인지, 원래 이런 형식으로 만들어진 것인지 의문입니다. 제 관점으로 보기에는 이미 serializeUser 단계에서 user 정보를 갖고 있고 여기서 db 와 비교해도 될것같은데 왜 굳이 한번더 deserializeUser 로 user 정보를 넘기는 지 궁금합니다.
4년 전
serializeUser에서 done으로 넘겨주는 게 세션메모리에 유저 정보를 저장하는건데요. 유저 정보를 통째로 저장하면 메모리를 많이 차지하므로 id만 빼서 저장하는 겁니다. deserilaizeUser에서는 그 아이디를 다시 전체 정보로 복구하는 것이고요. 메모리 용량 효율을 위해 마련된 단계들입니다.
4년 전
아 그런 이유 였군요.. 설명해주셔서 감사합니다!
4년 전
제가 부족한 탓에 이해가 안가는 부분이 있습니다. 도중에
passport.deserializeUser 부분에서 User.findById() 라고 돼있는데 여기서 User 는 무엇인지 모르겠습니다. import 된건 Users 고 매개변수로 받은건 user 인데 이 User 는 어디서 온 건 지 이해를 못해서요..
4년 전
Users 로 하니 잘 동작하는 것 같은데 단순 오타인걸 제가 오해한것 같습니다
4년 전
passport로 여러명 동시에 로그인이 가능한가요??
4년 전
여러 명이 동시에 로그인해야하는 상황이 무엇인가요?
5년 전
선생님, 무식한 질문이긴 합니다만 저는 jwt 를 사용해보려 해서 serializeUser, deserializeUser 함수를 사용하지 않았습니다. 그렇다면 제 경우엔 굳이 passport-local 의 Strategy 안에서 입력받은 비밀번호와 DB저장된 암호를 대조할 필요가 있나 고민하게 되었습니다. router 안에서 확인 하고 jwt를 발급및 검증할 때나 passport-jwt해도 되지 않을까요?
5년 전
jwt를 사용하신다면 기존과는 많이 달라집니다. 다만 passport-jwt가 발급 부분은 되는데 검증 모듈로는 사용되지는 않습니다. 검증은 직접 미들웨어식으로 구현하셔야 합니다.
5년 전
그럼 ejs에 user이름을 확인보고싶으면 res.render('index', info: {username: req.user.nickname } 이런식으로 하면 되나요?
5년 전
res.render('index', { username: req.user.nickname }) 하시면 됩니다.
5년 전
passport.js의 21번 라인을 보시면 if (findError) return done(findError);으로 되어있습니다. 여기서 return없이 done을 호출하는게 아닌, return을 앞에 붙인 이유가 그 다음줄이 실행되지 않도록 하기위해서인가요? 아니면 다른 이유가 있으신가요?
5년 전
네네 return으로 함수를 명료하게 종료하기 위함입니다.
5년 전
passport.serialize()를 처음 호출하는 부분이 어디인가요? serialize()는 user를 어디서 넘겨받는지 모르겠습니다.. 또 serialize()에서 deserialize()부분으로 넘어가는부분도 함수호출이없는데 어떻게 가능한지 이해가 안되네요 ..
5년 전
다 내부적으로 됩니다. 패스포트 내부에서요. 흐름을 외우는 수밖에 없습니다.
6년 전
안녕하세요 좋은 글 감사합니다. passport.js를 require 받은 다음에 passportConfig(); 하는 부분이 잘 이해되지 않습니다. passport.js에서 const passport = require('passport'); 한 다음에 passport에 전부 등록을 해주잖아요? 그러면 된 것 아닌가요??
6년 전
그러니까 passport.js에서 module.exports에 function을 넣는게 아니라 passport를 넣고, server.js에서

const passport = require('passport');
const passportConfig = require('./passport');
이렇게가 아니라

const passport = require('./passport');
이렇게 해줘도 되는 것 아닌가요????
6년 전
passport require하는 것은 passport.initialize와 passport.session 미들웨어를 사용하기 위함입니다. passportConfig()는 passport.js 내부 코드를 실행하기 위함입니다.(passport.use랑 passport.serialize, deserialize)
6년 전
안녕하세요~ 아직은 미숙해서 이런질문 드림을~ 양해 부탁드려요 passport로 세션사용하여 로그인 연동을 하여 만들어두었는데 에러가나거나 소스업데이트시 앱을 리스타트 하면 세션이 날아가는데 이러면 서비스를 사용하는 사용자는 모두 로그아웃 되어버립니다. 너무나 당연한 이야기지만 쿠키는 쓰고 싶지 않아서요. 스티키세션이나 이런걸 참고해서 보아야 할까요? 노드 접하기 이전에는 고민도 안했던거라 ㅎㅎ
6년 전
세션은 쿠키가 필수입니다. 쿠키 없이 하려면 아예 세션 없이 토큰으로 가셔야해요
6년 전
안녕하세요 이 강의를 바탕으로 따라해봣는데요.. 카카오 로그인을 제 카카오 아이디 말고 다른 카카오 아이디로도 해보려하는데 다른 아이디로 로그인해도 제 아이디로 로그인이 되서 문의 드렸습니다. 혹시 뭐가 문제일지 알 수 있을까요?
6년 전
일단 쿠키를 먼저 지워보세요. 그리고 로그아웃할 때 세션을 지우시나요?
6년 전
보통 사이트들보면 로그인페이지에서 로그인후에 다시 뒤로가기 누르면 로그인페이지가 안뜨잖아요??

근데 제가 지금 passport를 이용해서 페이스북으로 로그인기능을만들고있는데요.. 로그인은 잘되는데 다시 뒤로가기 누르면 로그인페이지가 뜹니다.. 안뜨는 방법없나요??..
사용자 정보도 세션에 저장해놨는데..
6년 전
UserSchema.static('findOne', function (email, callback) {
return this.find({ email:email }, callback);
}) 요거를 지우면 또 ValidationError: users7 validation failed: salt: Path `salt` is required., hashed_password: Path `hashed_password` is required. 이런에러가 뜨고요 ㅜ ㅜ
Usermodel findeone 함수를 사용하려면 static에서 정의해줘야 쓸수 있는거아닌가여 ?
6년 전
이건 몽구스 스키마 문제가 아니라 라우터에서 잘못된 정보가 들어오는 게 아닌가 하는 의심이 되네요.
6년 전
이렇게떠요 ㅠ ㅠ
6년 전
CastError: Cast to string failed for value "{ email: 'duatpwnd1@naver.com' }" at path "email" for model "users7"
6년 전
그게 혹시 무슨뜻인지? 알수있을까요 ㅠㅠ 이해가안되서용 ㅠㅠ
6년 전
passport local로 회원가입페이지 만들고있는데..CastError: Cast to string failed for value "{ email: 'c' }" at path "email" for model "users7" 자꾸 케스트 에러가뜹니다 .. 이유를 알수있을까요 ?
스키마 구조는 이렇게 되어있고 모듈화해서 불러오고있는데 왜 이럴까요
var UserSchema = mongoose.Schema({
email: {type: String, 'default': '', required:true, unique:true },
hashed_password: { type: String, required: true, 'default': '' },
salt: { type: String, required: true },
created_at: { type: Date, default: Date.now(), index: { unique: false } },
updated_at: { type: Date, default: Date.now(), index: { unique: false } }
})





app.use(passport.initialize());
app.use(passport.session());
// 다중서버접속//
app.use(cors())
/*
//몽구스 사용자 추가 응답 라우터 //
router.route('/process/listuser').post(require('./route/router').userAdd);
// 로그인 성공시 페이지 //
router.route('/login/success').post(require('./route/router').loginSuccess);
// 비밀번호 이메일 전송//
router.route('/email/password').post(require('./route/router').email)
*/

// passport strategy 설정 //
passport.serializeUser(function(user,done){
done(null,user);
})
passport.deserializeUser(function(user,done){
done(null,user);
})

passport.use('local-signup', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, function (req, email, password, done) {
console.log('요청들어왔다');
var database = app.get('database');
database.UserModel.findOne({'email': email}, function (err, user) {
if (err) {
console.log('계정조회중 오류발생');
return done(err);
}
if (user.length>0) {
console.log('기존에 있는 계정');
return done(null, false, {message:'존재하는 계정.'});
} else {
var user = new database.UserModel({'email': email, 'password': password });
user.save(function (err) {
if (err) {
console.log('err 발생');
console.dir(err);
}
console.log('추가성공');
return done(null, user);
})
}
})
}
))
router.route('/login').get(function (req, res) {
res.render('phone_login.ejs');
});
router.route('/join').get(function (req, res) {
res.render('email_join.ejs');
});
router.route('/join').post(passport.authenticate('local-signup', {
successRedirect: '/main',
failureRedirect:'/join'
}));
router.route('/main').get(function (req, res) {
res.render('main.ejs');
});
route_loader(app, router);
var server = http.createServer(app).listen(port, function () {
databaseloader(app, config);
console.log('익스프레스 서버 실행');
});
6년 전
혹시 { email: 'c' } 가 아니라 { email: { email: 'c' } } 하신 거 아닌가요?
6년 전
안녕하세요. 강좌들 잘 보고있습니다. 댓글은 처음 남겨보는데요, passport.authenticate 다음에 req.session 을 콘솔로 찍어보면 passport.user 가 들어있는것이 나오는데, 다음 요청에서는 req.session 에 passport.user가 없습니다. 브라우저에 세션저장소나 로컬저장소, 쿠키 저장소를 봐도 아무것도 안들어있네요.. 어떤 문제일까요? 프런트는 vue 로 작업되어있고, 백엔드는 강좌보고 그대로 따라 만들었습니다. localhost 8080포트에서 localhost 3000번 포트로 요청을 보내는 식이구요, cors 설정은 끝마쳤습니다. 오늘 몇시간을 붙들고있었는데 참 해결이 안되네요 ^^;
6년 전
express-session을 쓰실 때 브라우저의 쿠키저장소에 connect.sid같은 키가 없으면 문제가 있는 것입니다. 아마 8080포트에서 3000번 포트로 프록시 요청을 할 때 세션 연동 문제가 안 되는 것 같으니 그 부분을 검색해보세요.
6년 전
아 cors 설정이랑은 별개로 뭔가 추가 설정어 필요한가보군요. 가사합니다.
6년 전
app.js 에 cors 설정을 해줄때 app.use(require('cors')()) 로 끝내는것이 아니라, credentials: true 옵션을 주고,
app.use(require('cors')({
origin:['http://localhost:8080'],
methods:['GET','POST'],
credentials: true // enable set cookie
}))
프런트에서 axios 모듈을 사용할때도 withCredentials: true 옵션을 주니 잘 작동하는군요.

좋은 강좌 감사합니다. 이제 소셜로그인도 구현해봐야겠어요
6년 전
passport로 만드신 네이버로 로그인하기를 이용해보려 했는데 서버측오류가 발생하네요ㅜ
6년 전
확인했습니다. 빠른 시일 내에 수정하겠습니다.
6년 전
예제에 body-parser 패키지가 없는것같은데 post 파라미터 받을 수 있나요?
6년 전
바디 파서 미들웨어 추가하셔야 합니다~
6년 전
userSchema.methods.comparePassword = (inputPassword, cb) => { 이 부분을
userSchema.methods.comparePassword = function(inputPassword, cb){ 로 바꿔야 할까요? ^^

"Do not declare methods using ES6 arrow functions (=>). Arrow functions explicitly prevent binding this, so your method will not have access to the document and the above examples will not work."
6년 전
아 this를 쓰니 일반 함수 선언문을 써야겠군요.
7년 전
serializeUser, deserializeUser 이해하는데 많은 도움이 되었습니다. 감사합니다.
7년 전
server.js파일에 보면,app.use(passport.initialize()); // passport 구동
app.use(passport.session()); // 세션 연결

이 두부분이 계속 passport.initialize is not a function 또는 passport.session is not a function 이라는 오류가 계속 반복됩니다. 계속 머리굴려봤는데도 안됩니다.
환경은 ubuntu 16.04 lts입니다.
7년 전
수정했습니다. 감사합니다.
7년 전
일단 빠른 댓글 감사합니다.

저는 웹 페이지는 하지 않고 모바일에서만 동작하는 서버를 만들려고 하는데 그럼 passport 모듈을 통한 인증은 사용할 수 없는건가요? 토큰 방식을 찾아봐도 jwt만 나오는데 jwt는 서버에서 강제로 토큰을 만료 시킬 수 없어서 다른 토큰 방식을 찾고 있습니다. 혹시 모바일에서의 인증을 위한 모듈 있을까요? 조언 부탁드립니다.ㅜㅜ
7년 전
passport-google-token이나 passport-facebook-token같이 oauth2 방식의 토큰을 적용하셔야 할 것 같습니다. 굳이 passport를 사용하려고 하신다면요.
7년 전
안녕하세요. 글 잘 읽어봤습니다. 제가 iOS개발자라 서버쪽을 잘 모르고 처음 node js를 접해봐서 질문드립니다.
'/login' 이 로그인 api가 호출되면
router.post('/login', passport.authenticate('local', {
failureRedirect: '/'
}), (req, res) => {
res.redirect('/');
});

위의 authenticate 코드를 통해서

passport.use(new LocalStrategy({ // local 전략을 세움
usernameField: 'id',
passwordField: 'pw',
session: true, // 세션에 저장 여부
passReqToCallback: false,
}, (id, password, done) => {
Users.findOne({ id: id }, (findError, user) => {
if (findError) return done(findError); // 서버 에러 처리
if (!user) return done(null, false, { message: '존재하지 않는 아이디입니다' }); // 임의 에러 처리
return user.comparePassword(password, (passError, isMatch) => {
if (isMatch) {
return done(null, user); // 검증 성공
}
return done(null, false, { message: '비밀번호가 틀렸습니다' }); // 임의 에러 처리
});
});
}));

이 부분으로 넘어가는 건가요?
그리고 디비에서 정상적으로 이메일을 찾으면 return done(null, user); 을 호출하고
passport.serializeUser(function(user, done) {
done(null, user.id);
});
이 부분을 통해 세션을 저장하는건가요?

근데 로그인 한 후에 어떻게 클라이언트와 세션을 유지하는 건가요? 클라이언트에서는 다른 api를 사용할 때 어떤식으로 해야하는지 모르겠습니다.
제가 어설프게나마 알고있는 방법으로는 로그인을 하게되면 서버에서는 access token을 만들어서 디비에 저장하고 이 토큰을 클라이언트에 응답으로 전달해서 그 이후에 클라이언트는 다른 api를 사용할 때 이 토큰을 보내 서버에서는 이 토큰이 유효한지 보고 유효하다면 해당 api에 대한 응답을 해주고.. 이런걸 생각했는데
이 게시물이 제가 생각한 부분과 맞는지 모르겠습니다. 동일한 건데 제가 이해를 못한건지 아님 다른 부분인지 궁금합니다. 맞다면 어떤 흐름으로 로직이 흘러가는지 알고싶습니다. 감사합니다.
7년 전
네 전략 부분으로 넘어갑니다. serializeUser로 세션에 유저 아이디만 따로 저장하고요. 나중에 요청이 왔을 때 디비에서 유저 아이디로 유저 객체를 불러옵니다.

클라이언트(웹 브라우저)에 세션 아이디 정보를 가진 쿠키가 심어져있어 매 요청시 쿠키를 서버로 같이 보내 서버에서 세션 유무와 만료 여부를 체크합니다. 액세스 토큰 방식은 위의 방식과는 별개입니다. 현재 쿠키가 액세스 토큰을 대체한다고 생각하시면 됩니다.