Home passport 모듈
Post
X

passport 모듈

passport 모듈에 대해 알아보자.


passport 모듈

passport 모듈은 이름에서 알 수 있듯이 여권처럼 서비스를 이용할 수 있도록 사용자 인증을 구현할 때 사용합니다.

즉, Client가 Server에 서비스를 요청할 때 passport 모듈을 이용해 사용자 인증을 성공할 때만 응답해주는 것을 말합니다.


Strategy 전략

Passport.js에서 사용자는 필요따라 원하는 Strategy를 가져와 사용할 수 있습니다.

공식문서를 보면 다양한 Strategy가 존재함을 볼 수 있습니다.

  1. Local Strategy (passport-local 모듈) : 로컬 데이터베이스에서 로그인 인증 방식
  2. Social Strategy (passport-kakao, passport-twitter 등) : 소셜 네트워크 로그인 인증 방식

로그인 예제

기본적인 사용자 idpassport를 이용한 인증 방법인 local strategy(passport-local)를 사용해보겠습니다.


모듈 설치 및 연결

  • 설치

    1
    
    npm i express-session passport-local passport
    
  • 미들웨어 등록

    passport.initialize : req 객체에 passport 폴더의 index.js에 작성한 설정들을 입력합니다.

    passport.session : req.session 객체에 passport 정보를 저장합니다.

    req.session 객체는 express-session에서 생성하는 것이므로, passport 미들웨어는 express-session 미들웨어보다 뒤에 연결해야합니다.

    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
    34
    35
    36
    37
    
    // app.js
    ...
    const passport = require('passport');
    
    ...
    const passportConfig = require('./config/passport/index.js');
    
    ...
    const authRouter = require('./routes/auth'); // 인증 라우터
    
    const app = express();
    passportConfig(); // passport 설정
    
    ...
    app.use(cookieParser(process.env.COOKIE_SECRET));
    app.use(
       session({
          resave: false,
          saveUninitialized: false,
          secret: process.env.COOKIE_SECRET,
          cookie: {
             httpOnly: true,
             secure: false,
          },
       }),
    );
    
    // express-session에 의존하므로 뒤에 작성합니다.
    app.use(passport.initialize()); // req 객체에 passport 설정을 입력
    app.use(passport.session()); // req.session 객체에 passport정보를 추가 저장
    // passport.session()이 실행되면, 세션쿠키 정보를 바탕으로 해서 passport/index.js의 deserializeUser()가 실행됩니다.
    
    // router
    app.use('/auth', authRouter);
    
    ...
    
    

serializeUser & deserializeUser 작성

가장 중요한 passport.serializeUser, passport.deserializeUser를 작성합니다.

serializeUser는 <>req.login(user, …) 실행 시</u> 실행됩니다. 즉, 로그인 과정을 할때만 실행

deserializeUserserializeUser()가 done하거나 passport.session() 실행 시 실행됩니다.
즉, 서버 요청이 올때마다 항상 실행

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//config/passport/index.js
const passport = require("passport");
const local = require("./localStrategy");
const User = require("../models/user");

module.exports = () => {
  passport.serializeUser((user, done) => {
    done(null, user.id);
    // 로그인 시 req.session에 저장할 데이터를 결정합니다.
    // 자원낭비를 줄이기 위해 id만 저장합니다.
  });

  passport.deserializeUser((id, done) => {
    // req.session에 저장된 id로 DB 조회를해 사용자 정보를 req.user에 저장합니다.
    // 즉, id를 sql로 조회해서 전체 정보를 가져오는 복구 로직
    // 다른 미들웨어에서 req.user를 공통적으로 사용 가능
    User.findOne({ where: { id } })
      .then((user) => done(null, user))
      .catch((err) => done(err));
  });

  local();
};

localStrategy 작성

로컬 인증전략 절차 코드를 작성합니다.

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//config/passport/lacalStrategy.js
const passport = require("passport");
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");

const User = require("../models/user");

module.exports = () => {
  // 라우터에서 /login 요청이 오면 local설정대로 이쪽이 실행되게 된다.
  passport.use(
    "local",
    new LocalStrategy(
      {
        //* req.body 객체인자 하고 키값이 일치해야 한다.
        usernameField: "id", // req.body.email
        passwordField: "password", // req.body.password
        /*
            session: true, // 세션에 저장 여부
            passReqToCallback: false, 
            	express의 req 객체에 접근 가능 여부. true일 때, 뒤의 callback 함수에서 req 인자가 더 붙음. 
           		async (req, email, password, done) => { } 가 됨
            */
      },
      //* 콜백함수의  email과 password는 위에서 설정한 필드이다. 위에서 객체가 전송되면 콜백이 실행된다.
      async (id, password, done) => {
        try {
          // 가입된 회원인지 아닌지 확인
          const exUser = await User.findOne({ where: { id } });
          // 만일 가입된 회원이면
          if (user) {
            // 해시비번을 비교
            const result = await bcrypt.compare(password, user.password);
            if (result) {
              done(null, user); //? 성공이면 done()의 2번째 인수에 선언
            } else {
              done(null, false, { message: "비밀번호가 일치하지 않습니다." });
              // 실패면 done()의 2번째 인수는 false로 주고 3번째 인수에 선언
            }
            // done()을 호출하면, /login 요청온 라우터로 다시 돌아가서 미들웨어 콜백을 실행하게 된다.
          } else {
            done(null, false, { message: "가입되지 않은 회원입니다." });
          }
        } catch (error) {
          console.error(error);
          done(error); //? done()의 첫번째 함수는 err용. 특별한것 없는 평소에는 null로 처리.
        }
      }
    )
  );
};

Route 작성

로그인 처리를 담당하는 라우터. 로그인 인증에 관해 passport폴더로 인증전략을 요청한다.

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
34
35
36
37
38
const express = require("express");
const passport = require("passport");
const bcrypt = require("bcrypt");
const User = require("../models/user");

const router = express.Router();

// 로그인 요청
router.post("/login", (req, res, next) => {
  // local로 실행이 되면 localstrategy.js를 찾아 실행합니다.
  passport.authenticate("local", (authError, user, info) => {
    //? (authError, user, info) => 이 콜백 미들웨어는 localstrategy에서 done()이 호출되면 실행됩니다.
    //? localstrategy에 done()함수에 로직 처리에 따라 1,2,3번째 인자에 넣는 순서가 달랐는데 그 이유

    // done(err)가 처리된 경우
    if (authError) {
      console.error(authError);
      return next(authError); // 에러처리 미들웨어로 보낸다.
    }
    // done(null, false, { message: '비밀번호가 일치하지 않습니다.' }) 가 처리된 경우
    if (!user) {
      return res.redirect(`/?loginError=${info.message}`);
    }

    // done(null, User)가 처리된 경우
    return req.login(user, (loginError) => {
      // 만일 done(err) 가 됬다면,
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      // done(null, user)로 로직이 성공적이라면, 세션에 사용자 정보를 저장해놔서 로그인 상태
      return res.redirect("/");
    });
  })(req, res, next); // 미들웨어 내의 미들웨어에는 콜백을 실행시키기위해 (req, res, next)를 붙인다.
});

module.exports = router;

처리 과정

먼저 처리과정을 살펴보겠습니다.

  • 로그인 요청이 Route로 들어옵니다.
  • 미들웨어들을 지난 후, passport.authenticate() 메서드를 호출합니다.
  • authenticate 메서드는 사용자가 가져온 Strategy를 호출합니다. (config/passport/localStrategy.js)
  • Strategy를 실행하고 done() 메서드를 호출해 다시 passport.authenticate() 메서드로 돌아갑니다.
  • 실행된 done() 함수의 인자에 따라 여러 동작을 실행합니다.

    passport-done

  • 로그인 성공 시 사용자 정보 객체와 함께 req.logIn()를 자동으로 호출합니다.
  • req.logIn 메서드가 passport.serializeUser() 호출합니다. (passport/index.js)
  • req.sessionuser.id를 저장합니다.
  • passport.deserializeUser()로 넘어가 DB 조회를 통해 req.user 를 등록한 후, done() 메서드를 반환하여 req.logIn으로 돌아갑니다.
  • 미들웨어 처리 후, 세션 쿠키를 브라우저에 보냅니다.

장점

소셜 네트워킹의 등장으로 OAuth 공급자를 사용한 SSO(single sign on)이 널리 사용되는 인증 방법이 되었습니다.

API를 호출하는 서비스는 액세스를 보호하기 위해 토큰 기반 자격 증명이 필요해졌습니다.

Passport는 각 응용 프로그램마다 고유의 인증 요구 사항이 있음을 인식하여 전략(Strategy)로 알려진 인증 메커니즘으로 개별 모듈로 패키지할 수 있습니다.

즉, 불필요한 종속성을 만들지 않고 사용할 전략을 선택할 수 있다는 장점이 있습니다.


참조

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.