iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 26
0
Modern Web

用 JavaScript 打造全端產品的入門學習筆記系列 第 26

手刻登入功能:帳密檢查、保存登入狀態——全端刻意練習 IV

Login_Handcraft

在這個迅速數位化的時代,會員系統逐漸在各網站普及,而「註冊/登入/登出」是這個系統最基本的功能。但由於安全性考量,大多會找第三方資源來串接。然而理解這個操作過程,有助於未來開發其他相關功能時,擁有足夠的背景知識。本篇筆記則紀錄我手刻登入機制後的反思。

筆記目的

本篇筆記將解決以下問題:

  • 如何設計帳密檢查機制?
  • 如何保存使用者登入狀態?
    • HTTP 的無狀態機制(stateless)是什麼?
    • 如何實作 Cookie-based Authentication 的憑證機制?

誰適合閱讀:

  • 對 middleware 有一定程度理解者

參考資料:

 

登入功能

登入功能

登入功能概念圖 from Alpha Camp's material

用遊樂園來比喻,登入機制就是購票入場的概念。當我們使用帳號密碼登入後,會得到一個憑證,以使用任何網站提供的會員服務及功能。

由於 HTTP 的既有限制,通常拆成兩階段來實作:

階段一:帳密檢查機制

帳密檢查機制

帳密檢查機制流程圖 from Alpha Camp's material

將流程拆解,帳密檢查分兩個步驟:

  1. 接收 form post 的 帳號密碼資訊
  2. 比對資料庫中的帳號密碼
    • 帳密正確則進入歡迎頁面
    • 只要其一錯誤則導回登入頁面,並給予提示

登入表單的路由

app.post('/login', (req, res) => {
  const account = req.body
  const result = checkoutUser(account)

  if (result.status === 200) {
    res.redirect(`/dashboard?user=${result.user}`)
  } else {
    res.status(401).render('login', { account })
  }
})

比對帳密的演算法邏輯

function checkoutUser(account) {
  // Define users data
  // ...

  // Checkout account
  const result = users.find(user => user.email === account.email && user.password === account.password)

  // Define response
  const response = {
    user: null,
    status: 100
  }

  if (result) {
    response.user = result.firstName
    response.status = 200
  } else {
    response.status = 401
  }

  return response
}

階段二:保存使用者登入狀態

保存使用者登入狀態

保存使用者登入狀態流程圖 from Alpha Camp's material

做出這階段功能的關鍵是「讓 server 辨識:現在收到的 request 與稍早登入的 request 是同一位使用者」。

HTTP 的無狀態機制(stateless)

然在 HTTP 最早的設計中,每個 request 都是獨立判斷、並根據路由設計做出回應。之前的連線不需要被紀錄,這就是所謂「HTTP Protocol is Stateless」。

Cookie-based Authentication

為了克服「Stateless」,這次的練習採用相對簡單的 Cookie-based Authentication 來設計的憑證機制。

cookie & session 示意圖

cookie & session 示意圖 from Alpha Camp's material

  • Cookie:是內建於瀏覽器中的本地儲存空間,通常用以描述「client 與 server 目前溝通狀態」。
  • Session:是為了提高資安強度,設計在應用程式伺服器中的一個變數,用以儲存與 cookie 對應的機敏資料(如帳號密碼),並且提供一組不具意義的唯一代碼存放在 cookie 中。

實作憑證機制

我覺得憑證的概念,本身不難理解,重點在於不熟悉如何操作 cookie 和 session,於是在攻略完相關觀念後,直接 Google 「Cookie-based Authentication express」就能找到很多相關資料了。而我採用 Cookie-ParserExpress-Session 這兩個套件來輔助。

完整程式碼可以參考我的 GitHub repo

  1. 引入並設定套件
const cookieParser = require('cookie-parser')
const session = require('express-session')

// initialize cookie-parser to allow us access the cookies stored in the browser. 
app.use(cookieParser())

// initialize express-session to allow us track the logged-in user across sessions.
app.use(session({
  key: 'user_sid',
  secret: 'someRandomStuffs',
  resave: false,
  saveUninitialized: false,
  cookie: {
    expires: 6000000
  }
}))
  1. 設定 session 檢察機制的 middleware
// This middleware will check if user's cookie is still saved in browser and session is not set, then automatically log the user out.
// This usually happens when you stop your express server after login, your cookie still remains saved in the browser.
app.use((req, res, next) => {
  if (req.cookies.user_sid && !req.session.user) {
    res.clearCookie('user_sid')
  }
  next()
})


// middleware function to check for logged-in users
const sessionChecker = (req, res, next) => {
  if (req.session.user && req.cookies.user_sid) {
    const account = req.session.user
    const result = checkoutUser(account)
    const user = result.user
    res.redirect(`/dashboard?user=${user}`)
  } else {
    next()
  }
}
  1. 設計 login/logout 相關路由
app.post('/login', (req, res) => {
  const account = req.body
  const result = checkoutUser(account)

  if (result.status === 200) {
    req.session.user = account
    res.redirect(`/dashboard?user=${result.user}`)
  } else {
    res.status(401).render('login', { account })
  }
})

// route for user's dashboard
app.get('/dashboard', (req, res) => {
  if (req.session.user && req.cookies.user_sid) {
    const user = req.query.user
    res.render('dashboard', { user })
  } else {
    res.redirect('/login')
  }
})


// route for user logout
app.get('/logout', (req, res) => {
  if (req.session.user && req.cookies.user_sid) {
    res.clearCookie('user_sid')
    res.redirect('/')
  } else {
    res.redirect('/login')
  }
})

 


閱讀更多

Infinite Gamer
關於本系列更多內容及導讀,請閱讀作者於 Medium 個人專欄 【無限賽局玩家 Infinite Gamer | Publication – 】 上的文章 《用 JavaScript 打造全端產品的入門學習筆記》系列指南


上一篇
JavaScript 物件導向白話文筆記——全端開發者內功 I
下一篇
深入淺出 Middleware——全端開發者內功 II
系列文
用 JavaScript 打造全端產品的入門學習筆記30

尚未有邦友留言

立即登入留言