iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 19
0
自我挑戰組

如何成為工程師? (從工地到前端工程師)系列 第 19

[Day 19] Node JS 如何用Passport.js 進行認證? let me show you!

Passport.js 是一個使用者登入跟認證系統. 它有提供很多套件(strategy)給很多不同第三方認證 像facebook, google. 這個教學一樣會分成兩段, 第一段model篇, 第二段routes & view 篇

以下的gif 就是我們要做的功能.

sign in

sign up

Model

  1. 首先我們要先安裝, bcrypt.js, express-validator, passport, passport-local,

package.json

// package.json
 ...
  "dependencies": {
    "bcryptjs": "^2.4.3",
    "body-parser": "~1.17.1",
    "connect-flash": "^0.1.1",
    "cookie-parser": "~1.4.3",
    "debug": "~2.6.3",
    "ejs": "~2.5.6",
    "express": "~4.15.2",
    "express-messages": "^1.0.1",
    "express-session": "^1.15.3",
    "express-validator": "^3.2.0",
    "mongoose": "^4.10.6",
    "morgan": "~1.8.1",
    "passport": "^0.3.2",
    "passport-local": "^1.0.0",
    "serve-favicon": "~2.4.2"
  }

  1. 我們要建立一個user model, 然後建立一些 user 需要的方法 user.js
// model/users.js
//先載入我們要的library
var mongoose = require('mongoose')
var bcrypt = require('bcryptjs')

//創造資料庫需要的欄位(schema)
var UserSchema = mongoose.Schema({
  username: { type: String, index: true},
  password: { type: String}
})


var User = module.exports = mongoose.model('User', UserSchema)

// 建立createUser方法, 然後用bcrypt加密 + 存檔
module.exports.createUser = function(newUser, callback) {
  bcrypt.genSalt(10, function(err, salt) {
    bcrypt.hash(newUser.password, salt, function(err, hash){
      newUser.password = hash
      newUser.save(callback)
    })
  })
}

// getUserByUsername, 用username來找使用者
module.exports.getUserByUsername = function(username, callback) {
  var query = { username: username }
  User.findOne(query, callback)
}

// getUserById, 用id來找使用者
module.exports.getUserById = function(id, callback) {
  User.findById(id, callback)
}

// comparePassword, 當使用者登入的時候我們要比對登入密碼跟我們資料庫密碼相同
module.exports.comparePassword = function(candidatePassword, hash,callback) {
  bcrypt.compare(candidatePassword, hash, function(err, isMatch) {
    if(err) throw err
    callback(null, isMatch)
});
}
  1. 在我們的app.js 設定好mongoDB 跟 passport.js app.js
var passport = require('passport')
var LocalStrategy = require('passport-local')
var mongoose = require('mongoose')
var flash = require('connect-flash');
var users = require('./routes/users');
...
mongoose.connect('mongodb://localhost/loginappv2');
var db = mongoose.connection;
...
app.use(passport.initialize());
app.use(passport.session());
  1. 在我們的routes/user.js, 寫好相關的routes user.js

// GET 登入頁面
router.get('/signin', function(req, res, next) {
  console.log(res.locals)
  res.render('signin');
});

// POST 登入頁面
router.post('/signin',
  passport.authenticate('local', {
    successRedirect: '/users/profile',
    failureRedirect: '/users/signin',
    failureFlash: true
  }),
  function(req, res) {
    res.redirect('/users/profile')
});

// GET 註冊頁面
router.get('/signup', function(req, res, next) {
  res.render('signup', {errors: ''});
});

// POST 註冊頁面
router.post('/signup', function(req, res, next) {
  // Parse Info
  var username = req.body.username
  var password = req.body.password
  //Create User
  var newUser = new User({
    username: username,
    password: password
  })
  User.createUser(newUser, function(err, user){
    if(err) throw err;
  })
  res.redirect('/users/signin')
});

// GET 登入後的 profile 頁面
// 這邊有用ensureAuthenticated 來看使用者是不是已登入過, 如果沒有就不可以來這一頁
router.get('/profile', ensureAuthenticated, function(req, res, next) {
  console.log(req.user)
  res.render('profile', {
    user: req.user.username
  });
});

// GET 登出
router.get('/logout', function(req, res, next) {
  req.logout()
  req.flash('success_msg', 'You are logged out')
  res.redirect('/users/signin')
})
module.exports = router;
  1. 在一樣的檔案 再加入一些passport 相關的 function.
  • ensureAuthenticated 檢查使用者有沒有登入
  • 使用passport 本身的登入系統
  • 我已經先註冊了app.session(),接著需要passport內建的serializeUser、deserializeUser,兩個函式分別對應將資料存入Session(放入req.session.passport.user,express.session()中間件會處理session存放)和存Session取出資料並放入req.user中,這兩個操作不論路徑有沒有註冊都會視為套用(可以理解為Session處理的中間件)。

function ensureAuthenticated(req, res, next){
  if(req.isAuthenticated()){
    return next();
  } else {
    req.flash('error_msg', 'you are not logged in')
    res.redirect('/users/signin')
  }
}

passport.use(new LocalStrategy(
  function(username, password, done) {
    User.findOne({ username: username }, function(err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: 'Incorrect username.' });
      }
      User.comparePassword(password, user.password, function(err, isMatch){
        if(err) throw err
        if(isMatch) {
          return done(null, user)
        } else {
          return done(null, false, {message: 'Invalid password'})
        }
      })
    });
  }
));

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

passport.deserializeUser(function(id, done) {
  User.getUserById(id, function(err, user) {
    done(err, user);
  });
});

參考文件


上一篇
[Day 18] Node 串金流, 不然怎麼跟客戶收錢 Part II
下一篇
[Day 20] 用Passport JS 認證 Part II
系列文
如何成為工程師? (從工地到前端工程師)30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言