iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 30
1
自我挑戰組

轉職道上的萌芽人生 − 自學程式開發ing系列 第 30

Day 30- 191016學習筆記 passport.js 使用者認證

終於到最後一天啦!今天要學習使用 passport 來達成 使用者認證。其中提供了超過五百種 Strategies,包括可以使用獨立的使用者帳戶來進行認證,也可以使用第三方認證系統,如 Facebook、Twitter 等。在這裡我將學習使用 passport-local,來完成我的圖書館網站的認證系統。


passport 簡介

Method:

  • serializeUser():可設定要將哪些 user 資訊,儲存在 Session 中的 passport.user。(如 user._id)
  • deserializeUser():可藉由從 Session 中獲得的資訊去撈該 user 的資料。

Middleware:

  • passport.initialize():確認 passport.user 是否已存在,若沒有則初始化一個空的。
  • passport.session():用以處理 Session。若有找到 passport.user,則判定其通過驗證,並呼叫 deserializeUser()。
  • passport.authenticate():用以驗證使用者。可設定要採用的 Strategy、驗證成功與失敗導向的頁面。
  • ensureAuthenticated():客製化 middleware,用以確認其為「已驗證」的狀態,並導向頁面。

設定 Strategies:

  • passport.use({設定參數},new <Strategies>(<verify callback>))
    以此使用 Strategies:1.設定參數 2.new 一個想使用的 Strategy 3.設定用來驗證的 callback。

passport-local 搭配 MongoDB

Model

首先先建立 Model,以設定儲存在 MongoDB 的資料結構。
models/user.js

const mongoose = require('mongoose')
const Schema = mongoose.Schema

const UserSchema = new Schema({
  username: { type: String, required: true, min: 6, index: true },
  password: { type: String, required: true, min: 6 },
  firstname: String,
  lastname: String,
  email: { type: String }
})

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

設定 passport

接著將 passport 放進 app 的流程中。
app.js

  • 首先要先進行 (de)serializeUser
// 從user資料中撈ID
passport.serializeUser(function (user, done) {
  done(null, user._id)
})
// 以ID去撈user資料
passport.deserializeUser(function (id, done) {
  User.findById(id, function (err, user) {
    done(err, user)
  })
})
  • 設置 Strategies,以驗證 login 為例:
  • LocalStrategy 預設會從 req.body、req.query 中取得 username、password。也可以另外設定參數。
    這裡使用了 connect-flash 來顯示訊息提示。
    以 bcrypt-nodejs 來對 password 進行加密
const LocalStrategy = require('passport-local').Strategy
const User = require('./models/user')
const flash = require('connect-flash')
const bcrypt = require('bcrypt-nodejs')

app.use(flash())

passport.use('login', new LocalStrategy({
  passReqToCallback: true
},
(req, username, password, done) => {
  User.findOne({ username: username }, (err, user) => {
    const isValidPassword = (user, password) => {
        return bcrypt.compareSync(password, user.password)
      }
    if (err) { return done(err) }
    if (!user) { return done(null, false, req.flash('info', 'User not found.')) }
    if (!isValidPassword(user, password)) { return done(null, false, req.flash('info', 'Invalid password')) }
    return done(null, user) })}))
  • 這裡一起將 express-session 放進去,而要注意的是需放在 passport.session() 之前。
    (畢竟得先有 session 才能給 passport.session() 處理。)
const passport = require('passport')
const LocalStrategy = require('passport-local').Strategy
const session = require('express-session')

app.use(session({
  secret: 'secret',
  saveUninitialized: true,
  resave: false
}))

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

View

這部分跟前面的 Form 差不多。
views/login 為例:

extends layout
block content
  h1= title
  form(method='POST' action='')
    div
      label(for='username') Username:
      input#username(type='text', placeholder='username' name='username' value=(undefined===user ? '' : user.username) required='true' autofocus)
      label(for='password') Password:
      input#password(type='password' name='password'  required='true')
    button(type='submit') Login
  if errors 
   ul
    for error in errors
     li!= error.msg        

Route

將需要用到的 route 設置好。
routes/users.js

const express = require('express')
const router = express.Router()
const userController = require('../controllers/userController')
router.get('/')
router.get('/signup', userController.user_signup_get)
router.post('/signup', userController.user_signup_post)
router.get('/login', userController.user_login_get)
router.post('/login', userController.user_login_post)
router.get('/logout', userController.user_logout)
router.get('/profile', userController.user_profile)
module.exports = router

Controller

最後再設定與 Route 相對應的 Controller
controllers/userController.js
user_login_post 為例:

const passport = require('passport')
// 採用前面設定 'login' 的 Strategy
exports.user_login_post = [
  passport.authenticate('login', {
    successRedirect: '/users/profile',
    failureRedirect: '/users/login',
    failureFlash: true
  })]

參考:

[筆記] 透過 Passport.js 實作驗證機制
使用 Passport 實作 Nodejs 應用程式驗證機制
Node JS 如何用Passport.js 進行認證? let me show you!
NodeJS使用PassportJS處理認證流程


後續補充:

設定全域變數

  • app.locals:如此設定後,即可在所有的 view 中使用該變數。
    例如:在app.js中如此設定,就不必在所有的 route 中都重新設定一次 user 變數為 req.user。
    app.use((req, res, next) => {
      app.locals.user = req.user
      next()
    })
    

Logout

  • logOut():這裡 passport 提供了一個 Function 可以在登出時,將 session 清空。(注意:雖然官網上寫logout()、logOut()都可,但我實作只有 logOut() 可以。)
  • 另外也可使用另一個方法,自己設定清除 sesseion、cookie:參考
    exports.user_logout = (req, res) => {
      req.session.destroy(() => {
        res.clearCookie('connect.sid')
        res.redirect('/users/login')
      })}
    

練習用的圖書館網站新增的功能持續更新中,也有放上我的 GitHub,可作參考。


上一篇
Day 29- 191015學習筆記 Express - Session
系列文
轉職道上的萌芽人生 − 自學程式開發ing30

1 則留言

0
阿展展展
iT邦好手 1 級 ‧ 2020-02-08 05:59:31

恭喜完賽

iT邦新手 5 級 ‧ 2020-02-14 09:38:06 檢舉

哈哈 謝啦~

我要留言

立即登入留言