본문 바로가기
Javascript/Node.js

passport 적용

by 모스키토끼 2020. 7. 8.

Passport란?

현재 로그인한 유저, 클라이언트에는 쿠키를 서버에는 세션을 설정할 수 있는 미들웨어

NodeJS에 적용

  • passport를 애플리케이션의 미들웨어로 적용하기 전 로그인 전략(Strategy)을 짤 필요가 있음
    -> 네이버로그인으로 접근하는지, 자체 로그인 방식으로 접근하는지에 대한 전략
  • 전략 코드를 만들기 위해 passport라는 폴더를 만들어 전략 실행을 위한 index.js 파일과 local.js(로그인 전략) 파일을 만듦

passport/local.js

const passport = require('passport');
const { Strategy: LocalStrategy } = require('passport-local');
const bcrypt = require('bcrypt');
const db = require('../models');

module.exports = () => {
  passport.use(new LocalStrategy({
    usernameField: 'userId',
    passwordField: 'password',
  }, async (userId, password, done) => {
    try{
      const user =await db.User.findOne({ where: { userId }});
      if(!user) {
        return done(null, false, { reason: '존재하지 않는 사용자입니다!'});
      }
      const result = await bcrypt.compare(password, user.password);
      if(result) {
        return done(null, user);
      }
      return done(null, false, {reason: '비밀번호가 틀렸습니다!'});
    }catch(e){
      console.error(e);
      return done(e);
    }
}));
}
  • npm으로 받은 passport 미들웨어를 가지고 Strategy 객체를 생성
  • 유저 Id 필드는 userId, 비밀번호 필드는 password
  • done의 인자 -> (오류, 성공, 로직상 에러 발생 시 메시지)

passport/index.js

const passport = require('passport');
const local = require('./local');
const db = require('../models');
module.exports = () => {
  passport.serializeUser((user, done) => {
    return done(null, user.id);
  });

  passport.deserializeUser(async (id, done) => {
    try {
      const user = await db.User.findOne({
        where: { id },
        include: [{
          model: db.Post,
          as: 'Posts',
          attributes: ['id'],
        }, {
          model: db.User,
          as: 'Followings',
          attributes: ['id'],
        }, {
          model: db.User,
          as: 'Followers',
          attributes: ['id'],
        }],
      });
      return done(null, user); // req.user에 저장됨
    } catch (e) {
      console.error(e);
      return done(e);
    }
  });

  local();
}
  • passport.serializeUser
    - login router에서 req.login을 호출했을 때 실행됨(로그인 요청 시 실행)
  • local 로그인 전략에서 에러가 없는 경우 서버 쪽에 [{ id: 3, cookie: 'asdfe' }] 형태로 쿠키 정보 관리
    -> 프런트에서 서버로 쿠키를 보내면 그 쿠키가 무슨 id에 연결되어 있는지 알 수 있음
  • passport.deserializeUser
    - 클라이언트에서 요청을 보내올 때마다 실행
    (실무에서는 서버에 가해지는 무리를 줄이기 위해 deserializeUser 결과물을 캐싱)
  • 서버는 메모리에서 id 정보밖에 찾을 수 없으므로 디비에서 나머지 정보들을 불러옴
    -> deserialsize가 req.user를 만들어줌
  • local()로 전략 코드를 연결

index.js

const express = require('express');
const morgan = require('morgan');
const db = require('./models');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const expressSession = require('express-session');
const dotenv = require('dotenv');
//현재 로그인한 유저 찾기, 클라이언트엔 쿠키 서버엔 세션 설정
const passport = require('passport');
const passportConfig = require('./passport');
...

dotenv.config();
const app = express();
passportConfig();
...

app.use(cors({
  //이 두 속성의 값을 true를 줘야 쿠키를 줄 수 있다. 도메인이 같으면 필요 없음.
  origin: true,
  credentials: true, //이 부분은 클라이언트랑 서버랑 둘 다 설정해주어야한다.
}));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(expressSession({
  resave: false,
  saveUninitialized: false,
  secret: process.env.COOKIE_SECRET,
  //자바스크립트로 쿠키 접근 금지 설정
  cookie:{
    httpOnly: true,
    secure: false, //https를 쓸 때 true;
  },
  name: 'bigrings',
}));
app.use(passport.initialize());
app.use(passport.session());
...
  • 프로젝트의 루트 index에서 passport를 실행해주어야 미들웨어로서 작동
  • passportConfig라는 이름으로 passport/index.js의 export를 받아와 실행
  • 만약 도메인이 다르다면 cors를 사용하여 origin과 credentials 속성 값을 true로 주어야 쿠키를 줄 수 있음
  • credential은 클라이언트 쪽에서도 쿠키를 보내려면 credential값을 true로 주어야 함
  • cookieParser의 인자 값과 expressionSession의 secret 속성 값은 쿠키의 값을 암호화하여 쿠키의 용도를 가리기 위한 값
  • 소스 자체가 털릴 가능성이 존재하기 때문에. env를 사용하여 값을 넣어줌
  • expressionSession의 cookie 객체의 httpOnly 속성 true -> 자바스크립트로 쿠키 접근 금지
    secure 속성 true -> https를 사용하는 경우
  • passport.initialize(), passport.session()을 미들웨어로 등록하면 passport 사용 가능
    (passport.session()은 expressionSession을 사용하기 때문에 expressionSession 밑에서 실행되어야 한다.)

routes/user.js

router.post('/login', (req, res, next) => {
  //콜백 함수 인자로써 local 파일의 done에서 넣어준 값들이 들어온다.
  //카카오 네이버 로그인이면 그 전략 파일을 불러와라
  passport.authenticate('local', (err, user, info) => {
    if(err){
      console.error(err);
      return next(err);
    }
    if(info){
      return res.status(401).send(info.reason);
    }
    return req.login(user, async (loginErr) => {
      try {
        if (loginErr) {
          return next(loginErr);
        }
        //얕은 복사
        const fullUser = await db.User.findOne({
          where: {id: user.id},
          include: [{
            model: db.Post,
            as: 'Posts', //associate에서 as를 넣어줬으면 똑같이 넣어주어야한다.
            attributes: ['id'],
          }, {
            model: db.User,
            as: 'Followings',
            attributes: ['id'],
          }, {
            model: db.User,
            as: 'Followers',
            attributes: ['id'],
          }],
          attributes: ['id', 'nickname', 'userId'],
        });
        console.log(fullUser);
        return res.json(fullUser);
      }catch (e) {
        next(e);
      }
    });
  })(req, res, next);
});
  • 로그인 요청을 처리하는 라우터
  • 로그인 전략 코드에서 사용한 done의 인자로 넣어준 값들이 여기서(passport.authenticate) 사용
  • req.login을 사용하여 서버 쪽 세션에 userId와 쿠키 값을 저장
  • 필요한 정보들을 디비에서 가져와 클라이언트로 보내줌

Reference

https://www.inflearn.com/course/react_nodebird

 

React로 NodeBird SNS 만들기 - 인프런

리액트&넥스트&리덕스&리덕스사가&익스프레스 스택으로 트위터와 유사한 SNS 서비스를 만들어봅니다. 끝으로 검색엔진 최적화 후 AWS에 배포합니다. 중급이상 웹 개발 프레임워크 및 라이브러리

www.inflearn.com

'Javascript > Node.js' 카테고리의 다른 글

ORM(Sequelize) & 데이터베이스  (0) 2020.06.15
SuperTest 연습  (0) 2020.06.11
테스트 주도 개발(TDD)  (0) 2020.06.10
익스프레스JS  (0) 2020.06.10
NodeJS의 특징  (0) 2020.06.09

댓글