본문 바로가기
프로그래밍 개발/Express

Express - passport.js로 로그인 기능 적용하기

by Jinseok Kim 2021. 1. 18.
반응형

 

passport.js로 로그인 기능 적용하기

 

 

 

인증구현

app.post('/auth/login_process', //로그인 프로세스 경로로 설정
  passport.authenticate('local', { successRedirect: '/', //로그인 완료 후 등장할 경로 설정
                                   failureRedirect: '/auth/login' })); //로그인 실패 후 등장할 경로 설정
  • 로그인 폼에서 전송한 데이터를 받는 쪽을 passport로 전환하기 위해서는 일단 위의 코드를 main.js에 붙여넣기 해야한다.
  • 경로 또한 로그인을 할 수 있도록 하는 process 쪽으로 해줘야한다.

 

 

 

 

 

 

 

 

자격확인

 

main.js

//로그인 정보를 main.js로 가져옴
var authData = {
  email: 'k0502s2naver.com',
  password: '061599',
  nickname: 'jin seok'
};

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(
  {
    usernameField: 'email', //로그인 정보가 적힐 폼의 name 값을 원하는대로 바꿀 수 있다.
    passwordField: 'pwd'
  },
  //아래의 콜백함수의 인자로 폼에서 전달받은 username, password가 전해진다. 
  function (username, password, done) {
  //아이디 불일치, 비밀번호 불일치, 모두 일치 모든 상황에 if문을 이용하여 설정해두었다.
    if(username === authData.email){
      console.log(1); //아이디 정보가 일치시 실행
      if(password === authData.password){
        console.log(2); //패스워드 일치시 실행
        //done의 두번째 인자에 false가 아닌 값이 들어가면 passport에서 true로 인식하고 전달한다.
        return done(null, authData); 
      } else {
        console.log(3); //패스워드 불일치시 실행
        return done(null, false, {
          message: 'Incorrect password.'
        });
      }
    } else {
      console.log(4); //아이디 불일치시 실행
      return done(null, false, {
        message: 'Incorrect username.'
      });
    }
  }
));

 

 

 

 

 

 

 

 

세션이용

 

Passport.js는 내부적으로 express-session을 이용한다.

 

 

main.js

var authData = {
  email: 'k0502s@naver.com',
  password: '061599',
  nickname: 'jin seok'
};

//1. passport.js 기본 기능과 아이디 패스워드 방식 적용하기 위한 연동 세팅
var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

//2. passort.js 기본기능과 passport에서 세션기능을 사용할 수 있도록 미들웨이 적용
app.use(passport.initialize());
app.use(passport.session());

//5. 아이디 패스워드 일치 후 받은 authData 객체의 데이터를 serializeUser의 콜백함수 user 인자로 받아내고 done 메소드의 
// 두번째 인자에서 아이디 값인 user.email을 자동으로 새로 생성한 세션파일에 저장시킴.
passport.serializeUser(function(user, done) {
  done(null, user.email);
});

//6. deserializeUser는 로그인 완료 후의 페이지에서 어떤 작업을 하든 authData의 값을 done의 두번째
//인자로 지속적으로 호출시켜 항상 인증된 페이지 활동을 보장한다.
passport.deserializeUser(function(id, done) {
 done(null, authData);
  });

//4. passport가 연동되어 로그인 정보을 받은 폼에서 정보를 받은 후 아래의 코드들로
// 아이디 패스워드 일치 불일치 판단여부 결정.
passport.use(new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'pwd'
  },
  function (username, password, done) {
    console.log('LocalStrategy', username, password);
if(username === authData.email){
      console.log(1);
      if(password === authData.password){
        console.log(2);
 //일치가 판단되면 done의 두번째인자에 authData 객체안의 데이터가 serializeUser의 콜백함수 user인자로 전달
        return done(null, authData); 
      } else {
        console.log(3);
        return done(null, false, {
          message: 'Incorrect password.'
        });
      }
    } else {
      console.log(4);
      return done(null, false, {
        message: 'Incorrect username.'
      });
    }
  }
));

//3. 로그인 프로세스가 돌아갈때의 경로를 설정하여 passport에 연동하기
app.post('/auth/login_process',
  passport.authenticate('local', { successRedirect: '/',
                                   failureRedirect: '/auth/login' }));

 

 

  • 위의 결과처럼 로그인 하면 한 번의 serializeUser가 발동되고 페이지에 다른 활동을 할때마다 deserializeUser가 계속 발동되는 것을 터미널에서 확인할 수 있었다.
  • 그리고 로그인 후 user.email의 데이터가 session데이터에 적용된 것 또한 확인 할 수 있다.

 

 

 

 

 

 

 

로그인 확인과 로그아웃

 

로그인 및 로그아웃이 된 상태를 UI에 반영하기 위하여 수정

 

 

routes/auth.js

var express = require('express');
var router = express.Router();
var path = require('path');
var fs = require('fs');
var sanitizeHtml = require('sanitize-html');
var template = require('../lib/template.js');




router.get('/login', function(request, response){
  var title = 'WEB - login';
  var list = template.list(request.list);
  var html = template.HTML(title, list, `
    <form action="/auth/login_process" method="post">
      <p><input type="text" name="email" placeholder="email"></p>
      <p><input type="password" name="pwd" placeholder="password"></p>
      <p>
        <input type="submit" value="login">
      </p>
    </form>
  `, '');
  response.send(html);
});



//로그아웃시 passport에 인해 UI가 반응하기 위해서는 main.js에서 deserializeUser의
//콜백함수의 done의 두번째 인자 user값을 여기 밑의 get 메소드의 콜백함수의 첫번째 인자
//request의 프로퍼티로서 받아낸다는 약속이 있으므로 request에 메소드 .logout을 붙여주면 passport 기능
//으로 인해 데이터들이 삭제되는 로그아웃 기능이 실행되는 것이다.
router.get('/logout', function (request, response) {
  request.logout();
  request.session.save(function(){
    response.redirect('/');
  });
    });



  module.exports = router;

 

 

lib/auth.js

module.exports = {
//이 또한 isOwner의 콜백함수의 인자 request에 main.js에서 deserializeUser의 콜백함수의 done의 두번째 인자 user값을 
//받아냈기에 request.user라는 코드로 user 값을 가져오는 것이 가능해진 것이다.
    isOwner: function(request, response){
    if(request.user){ //받아온 user 값이 존재하면 true로 인식하여 if문이 실행된다.
        return true;
    } else{
        return false;
    }
}, statusUI: function(request, response){
    var authStatusUI = '<a href="/auth/login">login</a>'
    if(this.isOwner(request, response)){
                       //여기도 마찬가지로 request.user로 받은 것을 활용 시켰다.
        authStatusUI = `${request.user.nickname} | <a href="/auth/logout">logout</a>`;
    }
    return authStatusUI;
}


}

이제 로그인이 완료되었을때 UI부분이 반응하여 닉네임과 로그인 여부를 표시하는 것을 확인 할 수 있다. 로그아웃하면 다시 UI도 로그아웃을 표시한다.

 

 

 

 

 

 

플래쉬 메세지 구현

로그인 성공 및 실패시 메세지를 등장하게 하는 기능을 구현할 수 있다. 한 번 등장하고 자동으로 사라지는 것이 특징이다.

 

아래 사이트에 자세한 사용법이 있다.

github.com/jaredhanson/connect-flash

 

jaredhanson/connect-flash

Flash message middleware for Connect and Express. Contribute to jaredhanson/connect-flash development by creating an account on GitHub.

github.com

 

 

 

$ npm install connect-flash

위 코드를 명령하여 플래시 메세지를 구현할 수 있는 프로그램 다운할 수 있다.

 

 

var flash = require('connect-flash');


app.use(flash());
app.get('/flash', function(req, res){
  // Set a flash message by passing the key, followed by the value, to req.flash().
  req.flash('msg', 'Flash is back!!');
  res.send('flash');
});

app.get('/flash-display', function(req, res){
  // Get an array of flash messages by passing the key to req.flash()
  var fmsg =  req.flash();
  console.log(fmsg);
  res.send(fmsg);
});

위 두 코드를 main.js에 붙여준다. 주의할 점은 app.use(flash()); 미들웨이는 세션에 비롯된 것이므로 세션 적용 미들웨이 밑에 붙여줘야 한다.

 

 

 

main.js

var express = require('express');
var app = express();
var fs = require('fs');
var bodyParser = require('body-parser');
var compression = require('compression');
var helmet = require('helmet')
app.use(helmet());
var session = require('express-session')
var FileStore = require('session-file-store')(session)
var flash = require('connect-flash'); //플래시 메세지 기능 연동

app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(compression());
app.use(session({
  secret: 'asadlfkj!@#!@#dfgasdg',
  resave: false,
  saveUninitialized: true, 
  store:new FileStore()
}))
app.use(flash()); //플래시 메세지 기능 미들웨이 추가



var authData = {
  email: 'k0502s@naver.com',
  password: '061599',
  nickname: 'jin seok'
};

var passport = require('passport')
  , LocalStrategy = require('passport-local').Strategy;

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

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

passport.deserializeUser(function(id, done) {
    console.log('deserializeUser', id);
 done(null, authData);
  });


passport.use(new LocalStrategy(
  {
    usernameField: 'email',
    passwordField: 'pwd'
  },
  function (username, password, done) {
    console.log('LocalStrategy', username, password);
if(username === authData.email){
      console.log(1);
      if(password === authData.password){
        console.log(2);
        return done(null, authData, {message: 'Welcome'}); //로그인 성공시 메세지 추가. success 객체에 담길 예정
      } else {
        console.log(3);
        return done(null, false, {
          message: 'Incorrect password.' //로그인 실패시 메세지이며 error 객체에 담길 예정
        });
      }
    } else {
      console.log(4);
      return done(null, false, {
        message: 'Incorrect username.' //로그인 실패시 메세지이며 error 객체에 담길 예정
      });
    }
  }
));

app.post('/auth/login_process',
  passport.authenticate('local', { 
 successRedirect: '/',
 failureRedirect: '/auth/login' ,
 failureFlash:true, //로그인 실패시 플래시 기능 true로 설정하여 스위치 온 
 successFlash:true}));// 로그인 성공시 플래시 기능 ture로 설정하여 스위치 온

 

 

routes/auth.js

var express = require('express');
var router = express.Router();
var path = require('path');
var fs = require('fs');
var sanitizeHtml = require('sanitize-html');
var template = require('../lib/template.js');




router.get('/login', function(request, response){
//아래와 같이 request을 통해 flash() 기능을 가져와 feedback 변수에 error 객체의 첫번째 프로퍼티 값 삽입
// error는 객체이며 플래시 기능 발동시 생성된다. 그리고 한 번만 프로퍼티 값이 등장하는 1회용이다.
var fmsg = request.flash();
  var feedback = '';
  if(fmsg.error){
    feedback = fmsg.error[0];
  }
  var title = 'WEB - login';
  var list = template.list(request.list);
  var html = template.HTML(title, list, `
     <div style="color:red;">${feedback}</div> //로그인 실패시 해당 UI 등장토록 함.
    <form action="/auth/login_process" method="post">
      <p><input type="text" name="email" placeholder="email"></p>
      <p><input type="password" name="pwd" placeholder="password"></p>
      <p>
        <input type="submit" value="login">
      </p>
    </form>
  `, '');
  response.send(html);
});




router.get('/logout', function (request, response) {
  request.logout();
  request.session.save(function(){
    response.redirect('/');
  });
    });



  module.exports = router;

 

 

routes/index.js

var express = require('express');
var router = express.Router();
var template = require('../lib/template.js');
var auth = require('../lib/auth');



 
router.get('/', function(request, response) { 
//마찬가지로 request을 통해 플래시 기능 가져와서 feedback에 success 객체의 첫번째 값 삽입
  var fmsg = request.flash();
  var feedback = '';
  if(fmsg.success){
    feedback = fmsg.success[0];
  }
    var title = 'Welcome';
    var description = 'Hello, Node.js';
    var list = template.list(request.list);
    var html = template.HTML(title, list,
      `<div style="color:blue;">${feedback}</div>
      <h2>${title}</h2>${description}
      <img src="/images/BR.jpg" style="width:300px; display:block; margin-top:10px;">
      `,
      `<a href="/topic/create">create</a>`,
       auth.statusUI(request, response)
    ); 
    response.send(html);
  });
   
  module.exports = router;

 

 

 

위와 같이 로그인 성공시와 실패시 플래시 메세지가 등장하는 것을 확인 할 수 있었다.

 

 

 

 

 

 

 

 

최종 리팩토링 정리

 

 

 

lib/passport.js

//exports을 이용하여 외부인 main.js에서의 'app'을 쓰기 위하여 'app'을 매개변수로 가져와 함수로 묶어주었다.
module.exports = function (app) {

    var authData = {
  email: 'k0502s@naver.com',
  password: '061599',
  nickname: 'jin seok'
      };

    var passport = require('passport'),
        LocalStrategy = require('passport-local').Strategy;

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

    passport.serializeUser(function (user, done) {
        done(null, user.email);
    });

    passport.deserializeUser(function (id, done) {
        done(null, authData);
    });

    passport.use(new LocalStrategy({
            usernameField: 'email',
            passwordField: 'pwd'
        },
        function (username, password, done) {
            if (username === authData.email) {
                if (password === authData.password) {
                    return done(null, authData, {
                        message: 'Welcome.'
                    });
                } else {
                    return done(null, false, {
                        message: 'Incorrect password.'
                    });
                }
            } else {
                return done(null, false, {
                    message: 'Incorrect username.'
                });
            }
        }
    ));
    return passport;
}

 

 

 

 

main.js

var express = require('express');
var app = express();
var fs = require('fs');
var bodyParser = require('body-parser');
var compression = require('compression');
var helmet = require('helmet')
app.use(helmet());
var session = require('express-session')
var FileStore = require('session-file-store')(session)
var flash = require('connect-flash');

app.use(express.static('public'));
app.use(bodyParser.urlencoded({ extended: false }));
app.use(compression());
app.use(session({
  secret: 'asadlfkj!@#!@#dfgasdg',
  resave: false,
  saveUninitialized: true,
  store:new FileStore()
}))
app.use(flash());

//외부에 있는 passport 기능을 구현하는 코드들을 모아둔 lib/passport.js에 app을 매개변수로 보내줌.
var passport = require('./lib/passport')(app);


app.get('*', function(request, response, next){
  fs.readdir('./data', function(error, filelist){
    request.list = filelist;
    next();
  });
});

var indexRouter = require('./routes/index');
var topicRouter = require('./routes/topic');
//이 또한 마찬가지로 passport 기능 코드를 가지고 있는 auth.js에 'passport'을 매개변수로 보내줌.
var authRouter = require('./routes/auth')(passport);

app.use('/', indexRouter);
app.use('/topic', topicRouter);
app.use('/auth', authRouter);

app.use(function(req, res, next) {
  res.status(404).send('Sorry cant find that!');
});
app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
});
app.listen(3000, function() {
  console.log('Example app listening on port 3000!')
});

 

 

 

 

routes/auth.js

var express = require('express');
var router = express.Router();
var path = require('path');
var fs = require('fs');
var sanitizeHtml = require('sanitize-html');
var template = require('../lib/template.js');

//외부인 main.js로부터 exports을 이용하여 'passport' 받아와 매개변수로 사용하여 함수 묶어줌.
module.exports = function (passport) {
  router.get('/login', function (request, response) {
    var fmsg = request.flash();
    var feedback = '';
    if (fmsg.error) {
      feedback = fmsg.error[0];
    }
    var title = 'WEB - login';
    var list = template.list(request.list);
    var html = template.HTML(title, list, `
      <div style="color:red;">${feedback}</div>
      <form action="/auth/login_process" method="post">
        <p><input type="text" name="email" placeholder="email"></p>
        <p><input type="password" name="pwd" placeholder="password"></p>
        <p>
          <input type="submit" value="login">
        </p>
      </form>
    `, '');
    response.send(html);
  });

  router.post('/login_process',
  //여기서 passport 기능을 사용함
    passport.authenticate('local', {
      successRedirect: '/',
      failureRedirect: '/auth/login',
      failureFlash: true,
      successFlash: true
    }));

  router.get('/logout', function (request, response) {
    request.logout();
    request.session.save(function () {
      response.redirect('/');
    });
});

  return router;
} 

 

 

 

반응형

댓글