본문 바로가기
[WEB]/Node.js

Node.js - Passport.js 사용

by hi_kmin6 2020. 5. 27.

현재 개발 진행 중인 채팅 서비스(express와 socket.io 사용)를 만드는 데 있어,

현재는 세션에 닉네임을 넣어두어 그것을 확인하여 사용자를 구분하고 있었습니다.

 

하지만 이제는 제대로 로그인과 로그아웃,

더 나아가 다른 사이트의 계정들을 이용해 해당 서비스를 이용하는 OAuth 또한 담아보고 싶어 passport.js를 공부해봤습니다.

 

더보기

아래의 코드를 어떻게 사용하는지 모르시겠다면?

passport.js의 코드는 대게 http://www.passportjs.org/docs/downloads/html/에 나와 있습니다. 실제 사용한 모습을 보시려면 github.com/dorakang612/chatPrac으로 가셔서 app.js, passport.js, globalRouter.js, userController.js, roomController.js를 확인하시면 됩니다.

 


Passport.js 란?

 

Passport.js는 인증 요청을 처리해주는 Node.js의 인증 미들웨어입니다.

 

기본적인 로그인 과정은 사용자가 username과 password를 제공하면 그것들이 일치하는지를 확인이 진행됩니다. 기본적인 로그인 방법 외에도 근래에는 SNS(Social Networking Service)가 많아졌고, Facebook과 Twitter에서 OAuth를 제공함에 따라 이런 SNS들의 계정을 가지고 서비스를 가입하고 인증하는 방법이 생겼습니다.

 

passport.js는 이러한 기본적인 로그인과 OAuth 인증 방법을 제공하는 패키지입니다. passport에서는 인증과정을 Strategy(전략)이라는 이름으로 관리합니다.  아래의 링크는 passport에서 제공하는 여러 Strategy들입니다. 찾아보시면 저희에게 익숙한 naver와 kakao의 strategy도 있습니다.

 

http://www.passportjs.org/packages/

 


Passport.js 사용법

 

현재 제가 진행한 정도는 기본적인 로그인 방법(local Strategy)을 사용하여 서비스에 로그인 및 로그아웃 하는 것 까지입니다. 따라서 그에 맞게 사용법을 작성해보겠습니다.

 

저는 Passport의 사용법을 4단계로 구분해봤습니다.

 

1. 설치

>npm i passport & passport-local

 

passport모듈을 설차하여 인증을 진행할 것이고, 그 인증 과정은 Local Strategy를 이용하겠습니다.

 

 

2. 미들웨어 등록

...

app.use(passport.initialize());
app.use(passport.session());

...

 

passport.initialize()를 작성하여 express app이 passport를 사용하도록 합니다.

 

passport.session()을 작성하여 passport가 session을 사용하도록 합니다.

 

 

3. Strategy 등록

passport.use(
  new LocalStrategy(
    {
      usernameField: "email",
    },
    function (username, password, done) {
      console.log(`LocalStrategy : ${username} , ${password}`);
      User.findOne({ email: username }, function (err, user) {
        if (err) {
          console.log("에러 발생");
          return done(err);
        }
        if (!user) {
          console.log("로그인 실패 - 등록되지 않은 계정");
          return done(null, false, { message: "Incorrect username." });
        }
        if (user.password !== password) {
          console.log(
            `로그인 실패 - ${user.password} & ${password} 비밀 번호 불일치`
          );
          return done(null, false, { message: "Incorrect password." });
        }
        console.log("로그인 성공");
        return done(null, user);
      });
    }
  )
);

재가 현재 사용하는 LocalStrategy입니다.

 

- 4번째 줄 : 기본적으로 passport는 username과 password를 활용하여 인증 과정을 진행하는데 저는 사용자의 구분 기준이 이메일이기 때문에 usernameField를 email로 했음을 알렸습니다.

- 6 ~ 26 번째 줄 : LocalStrategy의 콜백 함수입니다.

- 8번째 줄 : 몽고 디비에서 입력받은 이메일의 사용자가 있는지 찾습니다. 그 후 콜백 함수를 진행합니다.

- 9 번째 줄 : 에러가 발생하면 done함수의 첫 번째 인자로 error를 넣어 줍니다. 

- 13 번째 줄 : 해당 이메일을 가진 사용자가 없는 경우에는 done함수에 첫 번째 인자로 null, 두 번째 인자로 false, 세 번째 인자로 메세지 객체를 넣어줍니다.

- 17 번째 줄 : 사용자가 입력한 비밀번호와 해당 계정의 비밀번호가 일치하지 않으면 done함수에 첫 번째 인자로 null, 두 번째 인자로 false, 세 번째 인자로 메시지를 넣어줍니다.

- 24 번째 줄 : 사용자 입력 정보와 저장된 정보가 모두 일치하면 done함수에 첫 번째 인자로 null, 두 번째 인자로 사용자에 대한 정보를 넘겨줍니다.

 

콘솔 실행 문장들은 개발을 진행하며 변수의 값들을 확인하기 위해 적은 것이니 동일하게 작성하실 필요 없습니다.

 

 

4. 인증 성공 후 동작 등록

passport.serializeUser(function (user, done) {
  console.log("serializeUser : ", user);
  done(null, user.email);
});

passport.deserializeUser(function (email, done) {
  console.log("deserializeUser : ", email);
  User.findOne({ email: email }, function (err, user) {
    done(err, user);
  });
});

 

Passport의 사용법 2번째로 passport가 session을 사용하도록 했습니다. 바로 여기서 session을 사용합니다.

 

passport.serializeUser()는 Strategy를 통해 인증이 성공한 직후에 한 번만 실행하는 함수입니다.

serializeUser는 세션에 "passport":{"user":"test@test.com"}과 같이 사용자의 정보를 남깁니다. 그리곤 deserialize로 정보를 넘깁니다.

 

passport.deserializeUser()는 사용자가 같은 서비스 내에서 돌아다니며 새로운 페이지를 호출할 때마다 사용자의 정보를 request에 넣어 줍니다. 즉 req.user라는 객체를 사용할 수 있고, 이 객체에는 db에 저장해둔 사용자의 정보가 담겨있습니다.

 

export const home = async (req, res) => {
  if (!req.user) {
    res.redirect("/login");
  } else {
    console.log("로그인 성공 정보 : ", req.user);
    try {
      const rooms = await Room.find({});
      res.render("main", {
        inAppName: req.user.inAppName,
        rooms,
        title: "채팅방 목록",
        error: req.flash("error"),
      });
    } catch (error) {
      console.error(error);
      next(error);
    }
  }
};

위는 제 서비스의 홈페이지를 접속했을 때의 동작입니다. 2번째 줄을 보시면 만약 req.user 객체가 존재하지 않을 때, 즉 인증이 되지 않은 경우 /login으로 보내게 됩니다.  

 

 

5. 인증 진행

export const postLogin = passport.authenticate("local", {
  successRedirect: routes.home,
  failureRedirect: routes.login,
  failureFlash: "입력 정보를 다시 확인해주세요.",
});

로그인을 하기 위해 정보를 전송해 왔을 때의 라우터의 동작입니다.

 

passport.authenticate("local" , {}) 은 local strategy를 사용한다는 것입니다. 그리고 성공 시 실패 시 이동시킬 주소가 적혀있으며, 실패 메시지도 설정할 수 있습니다.

 

 

6. 로그아웃

인증을 성공하고 나면 세션에 사용자 식별 정보가 남아 있습니다. 이를 지우는 과정이 로그아웃입니다.

 

로그아웃 또한 passport에서 지원하는 기능으로 상당히 간편하게 진행 할 수 있습니다.

 

export const getLogout = (req, res) => {
  req.logout();
  res.redirect(routes.home);
};

위 코드는 logout으로 이동했을 경우의 동작입니다.

 

req.logout();이 바로 로그아웃을 진행하는 부분으로 이 메서드는 passport에서 기본적으로 제공하고 있으며 진행 후에는 세션에서 사용자의 정보가 지워지는 것을 확인 할 수 있습니다.

 

 

+) 추가 사항 - Flash Message 사용

로그인에 실패하는 경우는 3가지 정도일 것입니다. 

 

  1. 에러 발생
  2. 등록되지 않은 계정으로 시도
  3. 잘못된 비밀 번호 입력

위와 같은 이유로 로그인에 실패한 사용자에게 그 원인을 알려 계정을 확인하거나 비밀번호를 다시 입력하도록 유도하고 싶었습니다. 이에 따라 사용자에게 메시지를 보내는 것이 좋을 것 같아 flashmessage(일회성 메시지)를 사용하기로 했습니다.

 

>npm i connect-flash

 

flashmessage를 사용하기 위해 connect-flash 패키지를 설치합니다. flash message는 기본적으로 session을 사용하고 있기 때문에 세션 미들웨어 다음에 위치시켜주셔야 합니다.

 

// 세션 미들웨어 사용
app.use(sessionMiddleware);
// flash 미들웨어 사용
app.use(flash());

 

위와 같이 flashmessage를 사용할 준비가 되었으면 passport에서 설정을하고 값을 넣어줍니다.

 

export const getLogin = (req, res) => {
  res.render("login", { title: "로그인", error: req.flash("error") });
};

// passport의 local strategy를 사용해서 로그인을 진행합니다.
export const postLogin = passport.authenticate("local", {
  successRedirect: routes.home,
  failureRedirect: routes.login,
  failureFlash: true,
});

 

failureFlash : true(혹은 "직접 작성한 메시지"를 통해 flash Message를 설정할 수 있습니다.)를 통해서 passport에서 flash Message를 사용할 것임을 알렸습니다. 이는 실패시 메시지입니다. 물론 successFlash를 이용해서 성공했을 때도 이용 가능합니다.

 

passport.use(
  new LocalStrategy(
    {
      usernameField: "email",
    },
    function (username, password, done) {
      User.findOne({ email: username }, function (err, user) {
        if (err) {
          return done(err);
        }
        if (!user) {
          return done(null, false, { message: "등록되지 않은 계정입니다." });
        }
        if (user.password !== password) {
          return done(null, false, {
            message: "비밀번호가 일치하지 않습니다.",
          });
        }
        return done(null, user);
      });
    }
  )
);

 

위에서도 설명드렸었지만 실패시에는 return done(null, false, {message : "messages"}); 가 동작을 합니다. 이때 message가 flash Message입니다. 위의 메세지는 req.flash 객체내에 error라는 이름으로 등록이됩니다.

 

flash Message는 req.flash()혹은 req.flash("keyword")와 같이 한번 호출이 되면 사라집니다.

 

이를 이용해서 뷰 템플릿에 값을 넘겨주면 사용자에게 어떻게 로그인이 실패했는지 알려줄수 있습니다.

 


# 참고 자료

 

http://www.passportjs.org/docs/downloads/html/

 

Documentation

Overview Passport is authentication middleware for Node. It is designed to serve a singular purpose: authenticate requests. When writing modules, encapsulation is a virtue, so Passport delegates all other functionality to the application. This separation o

www.passportjs.org

 

https://opentutorials.org/course/3402

 

Passport.js - 생활코딩

수업소개 이 수업은 Node.js Express의 인증 미들웨어인 Passport.js의 사용법을 알려드리는 수업입니다.  수업대상 로그인/로그아웃/접근제한과 같은 인증 기능을 구현하고 싶은 분에게 필요한 수업��

opentutorials.org

 

 

 

 

'[WEB] > Node.js' 카테고리의 다른 글

React.js 와 Express 연동  (0) 2020.06.23
Node.js - Express와 Babel 그리고 Nodemon  (0) 2020.05.12
Node.js - Express로 서버 개발시 초기 설정  (0) 2020.05.06
Node.js -Nodemon  (0) 2020.04.24
Nodejs[Express] - 비밀키 관리  (0) 2020.04.13