iT邦幫忙

2026 iThome 鐵人賽

DAY 10
0

今日目標說明

昨天我們學會了如何核發 JWT。但如果我們要保護「新增寶可夢」、「修改圖鑑」這些 API,難道要在每一支 API 裡面都寫一次「檢查 Token 有沒有效」的 if...else 邏輯嗎?
絕對不要!這違反了 DRY (Don't Repeat Yourself) 原則。今天,我們將利用 Express 的 Middleware (中介軟體) 來打造一道全自動的防護罩。

【技術原理解析】什麼是 Middleware 的「洋蔥模型」?

在 Express 中,當前端發送一個請求過來,它並不是直接到達目標 API,而是會先經過一層一層的 Middleware,就像剝洋蔥一樣:

  1. 外層洋蔥 (Cors / BodyParser):先處理跨網域,並把 JSON 字串轉成物件。
  2. 中層洋蔥 (Auth Middleware)(我們今天的主角) 檢查 HTTP Header 有沒有帶 JWT Token?Token 是否過期?是假造的嗎?
    • 如果驗證失敗:直接退回 401 Unauthorized,連 API 都不給進。
    • 如果驗證成功:把解密後的玩家身分放進 req.user,然後呼叫 next() 繼續往內層走。
  3. 內層洋蔥 (真正的 API 邏輯):專心處理業務邏輯,且可以安心地直接使用 req.user,因為能走到這一步的人,絕對已經通過驗證了!

核心程式碼片段

我們建立 src/middleware/auth.ts 來實作這個守衛:

import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';

// 擴充 Express 的 Request 型別,讓我們可以把 user 塞進去
declare global {
  namespace Express {
    interface Request {
      user?: any;
    }
  }
}

export const requireAuth = (req: Request, res: Response, next: NextFunction) => {
  // 1. 從 Headers 找出 Token (格式通常是 Bearer xxxx)
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: '缺少授權 Token' });
  }

  const token = authHeader.split(' ')[1];

  try {
    // 2. 驗證這張通行證是不是我們簽發的、有沒有過期
    const decoded = jwt.verify(token, process.env.JWT_SECRET as string);
    
    // 3. 把解密出來的資訊 (如 userId, role) 塞進 req,方便後續 API 使用
    req.user = decoded;
    
    // 4. 驗證通過,放行!呼叫下一個處理者
    next();
  } catch (error) {
    // 只要過期或是被竄改,verify 就會噴 Error,進入這裡
    return res.status(401).json({ error: 'Token 無效或已過期' });
  }
};

使用方法非常優雅,只要在路由加上 requireAuth 即可:

// 這支 API 被防護罩包起來了!
app.use('/api/holdings', requireAuth, holdingRoutes);

實際畫面截圖

缺少授權Token

在沒有帶 Token 的情況下發送 GET 請求,被系統無情地用 401 Unauthorized 與「缺少授權 Token」擋下來的畫面。

小結

我們成功利用 Middleware 把驗證邏輯抽離出來,程式碼變得極度乾淨且安全!防護罩建立好了,明天我們終於可以來實作最核心的「取得寶可夢持有列表」API 了!


上一篇
DAY 9:登入實作與 JWT (JSON Web Token) 原理
系列文
就決定是你了!打造寶可夢持有追蹤系統 (React x Express)10
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言