NodeBird 3-1) Passport 모듈로 로컬 로그인 구현하기

30782 단어 node.jsnode.js

이번에는 서비스의 로그인 관련 기능을 구현하겠습니다

회원가입과 로그인 세션관리 등은 직접 구현할 수 있지만 복잡한 작업이 많으므로 Passport 라는 모듈을 사용하여 구현하는 것이 좋습니다

또한 서비스 계정이 아닌 다른 sns 계정으로 로그인이 가능하도록 구현하겠습니다

$ npm i passport passport-local passport-kakao bcrypt

passport모듈과 카카오 아이디로 로그인이 가능하도록 passport-kakao도 설치해줍니다
bcrypt는 암호화에 필요한 모듈로 비밀번호를 암호화 하여 데이터베이스에 저장하는 용도로 쓰일 것입니다

app.js에 코드를 추가합니다

const passport = require('passport');
const passportConfig = require('./passport');
...
passportConfig();
...
app.use(passport.initilaize());
app.use(passport.session());

passport.initilaize 미들웨어는 req에 passport 설정을 심고,
passport.session 미들웨어는 req.session 객체에 passport 정보를 저장합니다 req.session은 express-session에서 생성하는 것이므로 expresss-session 미들웨어보다 뒤에 연결해야 합니다

passport 폴더를 생성한 후 index.js파일을 생성합니다

const passport = require('passport');
const local = require('./localStrategy');
const kakao = require('./kakaoStrategy');
const User = require('../models/user');

module.exports = () => {
  passport.serializeUser((user, done) => {
    done(null, user.id);
  });

  passport.deserializeUser((id, done) => {
    User.findOne({ where: { id } })
      .then(user => done(null, user))
      .catch(err => done(err));
  });

  local();
  kakao();
};

serializeUser는 로그인 시 실행되며 done 함수를 통해 user.id를 세션에 저장합니다

deserializeUser은 매 요청 시 실행되며 세션에 저장된 user.id를 통해 데이터베이스에서 사용자를 조회하여 조회한 사용자 정보를 req.user에 저장합니다 이를 통해 앞으로 req.user를 통해 로그인한 사용자의 정보를 가져올 수 있게 합니다

이제 로컬 로그인을 구현 하겠습니다
로컬 로그인이란 다른 sns 서비스를 통해 로그인하지 않고 자체적으로 회원가입 후 로그인 하는 것을 의미합니다

로그인과 관련하여 구현할 것들은 회원가입, 로그인, 로그아웃이 있겠는데
로그인한 사용자는 회원가입과 로그인 라우터에 접근하면 안되며 로그인하지 않은 사용자는 로그아웃 라우터에 접근하면 안됩니다
그래서 라우터에 접근 권한을 제어하는 미들웨어를 먼저 만들어 줍니다

routes 폴더에 middlewares.js 파일을 생성합니다

exports.isLoggedIn = (req, res, next) => {
  if (req.isAuthenticated()) {
    next();
  } else {
    res.status(403).send('로그인 필요');
  }
};

exports.isNotLoggedIn = (req, res, next) => {
  if (!req.isAuthenticated()) {
    next();
  } else {
    const message = encodeURIComponent('로그인한 상태입니다.');
    res.redirect(`/?error=${message}`);
  }
};

이제 라우터에서 isLoggedIn과 isNotLoggedIn을 통해 로그인 여부를 검사하여 접근을 제어하게 할 수 있습니다

auth.js 파일을 생성하고 로그인 관련 라우터를 만들어줍니다

const express = require('express');
const passport = require('passport');
const bcrypt = require('bcrypt');
const { isLoggedIn, isNotLoggedIn } = require('./middlewares');
const User = require('../models/user');

const router = express.Router();

router.post('/join', isNotLoggedIn, async (req, res, next) => {
  const { email, nick, password } = req.body;
  try {
    const exUser = await User.findOne({ where: { email } });
    if (exUser) {
      return res.redirect('/join?error=exist');
    }
    const hash = await bcrypt.hash(password, 12);
    await User.create({
      email,
      nick,
      password: hash,
    });
    return res.redirect('/');
  } catch (error) {
    console.error(error);
    return next(error);
  }
});

router.post('/login', isNotLoggedIn, (req, res, next) => {
  passport.authenticate('local', (authError, user, info) => {
    if (authError) {
      console.error(authError);
      return next(authError);
    }
    if (!user) {
      return res.redirect(`/?loginError=${info.message}`);
    }
    return req.login(user, (loginError) => {
      if (loginError) {
        console.error(loginError);
        return next(loginError);
      }
      return res.redirect('/');
    });
  })(req, res, next);  
});

router.get('/logout', isLoggedIn, (req, res) => {
  req.logout();
  req.session.destroy();
  res.redirect('/');
});


module.exports = router;

/auth/join은 회원가입 라우터로써 기본에 같은 이멜로 가입한 사용자가 있는지 조회한 뒤 있으면 회원가입 페이지로 돌려보내고 없다면 비밀번호를 bcrypt로 암호화 한 후 사용자를 데이터베이스에 등록합니다

/auth/login은 로그인 라우터로써 요청이 들어오면 passport.authenticate('local') 미들웨어가 로컬 로그인 전략을 수행합니다 미들웨어 안에 미들웨어를 넣었으므로 끝에 (req, res, next)를 붙여주었습니다 전략코드는 후에 작성하겠습니다

/auth/logout은 로그아웃 라우터로써 req.user 객체와 req.session 객체의 내용을 제거합니다

이제 로그인 전략을 구현하겠습니다
passport폴더에 localStrategy.js를 생성합니다

const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const bcrypt = require('bcrypt');

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

module.exports = () => {
  passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'password',
  }, async (email, password, done) => {
    try {
      const exUser = await User.findOne({ where: { email } });
      if (exUser) {
        const result = await bcrypt.compare(password, exUser.password);
        if (result) {
          done(null, exUser);
        } else {
          done(null, false, { message: '비밀번호가 일치하지 않습니다.' });
        }
      } else {
        done(null, false, { message: '가입되지 않은 회원입니다.' });
      }
    } catch (error) {
      console.error(error);
      done(error);
    }
  }));
};

LocalStrategy 생성자의 첫 번째 인수로 주어진 객체에서 usernameField는 아이디 역할을 하는 속성명을 passwordField에는 비밀번호 역할을 하는 속성명을 적어주면 됩니다 저희 서비스에서 email과 password가 각각의 역할을 수행하므로 적어줍니다

두 번째 인수인 async함수는 email과 password를 통해 로그인 성공여부를 체크하고 passport.authenticate의 콜백 함수인 done을 통해 성공여부와 사용자 정보를 넣어 보냅니다

좋은 웹페이지 즐겨찾기