前面已經完成 JWT 登入流程,使用者登入成功後,後端會簽發一組 Token。
但拿到 Token 之後,事情還沒結束。
之後使用者每次呼叫 API,後端都需要先確認:「這個人有沒有登入?帶來的 Token 有沒有問題?」
例如:
GET /api/notes
假設這支 API 只開放給登入使用者使用,Request 進來時,後端就要先檢查 JWT。如果 Token 不存在或驗證失敗,就直接擋下來;確認沒問題後,才讓 Request 繼續執行。
如果每一支 API 都自己寫一次 JWT 驗證,很快就會出現一堆重複程式碼。這時候就可以把驗證的工作交給 Middleware,讓所有需要登入的 API 共用同一套檢查流程。
前端拿到 JWT 之後,呼叫需要登入的 API 時,通常會把 Token 放進 Authorization Header。
格式會長這樣:
Authorization: Bearer <JWT>
Authorization 是 Header 的名稱,Bearer 表示後面帶的是一組 Token,最後的 <JWT> 才是真正的 JWT。
例如:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6...
前端可能會這樣送出:
fetch('/api/notes', {
headers: {
Authorization: `Bearer ${token}`
}
});
所以當這個 Request 到達後端時,Express 就可以從 req.headers.authorization 取得整串內容。
接下來的工作,就是把這串內容拆開,拿到真正的 Token,再交給 jwt.verify() 檢查。
這次要做的 Middleware 可以放在:
middlewares/
└── auth.js
完整程式碼如下:
const jwt = require('jsonwebtoken');
function verifyToken(req, res, next) {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
message: 'Unauthorized'
});
}
const [type, token] = authHeader.split(' ');
if (type !== 'Bearer' || !token) {
return res.status(401).json({
message: 'Unauthorized'
});
}
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({
message: 'Unauthorized'
});
}
}
module.exports = verifyToken;
這段 Middleware 可以按照 Request 進來之後的順序來看。
首先:
const authHeader = req.headers.authorization;
從 Request Header 裡取得 Authorization。
假設收到的是:
Bearer eyJhbGciOiJIUzI1NiIs...
接著用:
const [type, token] = authHeader.split(' ');
按照中間的空白切開。
原本:
Bearer eyJhbGciOiJIUzI1NiIs...
會變成:
type → Bearer
token → eyJhbGciOiJIUzI1NiIs...
所以這裡才會檢查:
if (type !== 'Bearer' || !token) {
return res.status(401).json({
message: 'Unauthorized'
});
}
如果沒有 Authorization,或格式不是 Bearer <Token>,就直接回傳 401 Unauthorized,Request 也不會繼續往下走。
格式確認沒問題之後,才是真正的 JWT 驗證:
const decoded = jwt.verify(token, process.env.JWT_SECRET);
jwt.verify() 會檢查 Token 是否有效,例如簽章是否正確、內容有沒有被竄改,以及 Token 有沒有過期。如果驗證失敗,就會進到 catch,最後回傳 401 Unauthorized。
只有驗證成功,才會繼續往下。
JWT 驗證成功後,jwt.verify() 會把 JWT 裡的 Payload 回傳出來,這個結果目前放在 decoded 裡。
例如前面登入時簽發 JWT:
const token = jwt.sign(
{
userId: user.id
},
process.env.JWT_SECRET,
{
expiresIn: '1h'
}
);
驗證之後,可能會得到:
{
userId: 123,
iat: 1759570000,
exp: 1759573600
}
這個 decoded 就是已經通過驗證的 JWT Payload。
接著:
req.user = decoded;
這一步的目的,是把驗證成功的使用者資訊交給後面的程式。
因為 decoded 只是 verifyToken 裡面的變數,後面的 Controller 並不能直接使用它。Middleware 執行完之後會呼叫 next(),讓 Request 繼續往下走,而後面的程式拿到的還是同一個 req,所以可以把資料放進 req.user,讓後面的 Controller 直接取用。
流程可以想成:
jwt.verify()
↓
得到 decoded
↓
req.user = decoded
↓
next()
↓
Controller
因此 Controller 裡就能直接寫:
const userId = req.user.userId;
拿到目前登入使用者的 userId。
這裡的 req.user 可以把它想成 Middleware 留給後面程式的「使用者身分資訊」。Middleware 先確認 Token 沒問題,再把確認過的結果交給後面的程式,Controller 就不用重新處理 JWT。
最後的:
next();
代表這一關通過了,Request 可以繼續往下一個 Middleware 或 Route Handler 前進。
有了 verifyToken 之後,就可以把它放到需要登入的 Route。
例如筆記 API:
const express = require('express');
const verifyToken = require('../middlewares/auth');
const router = express.Router();
router.get('/', verifyToken, async (req, res, next) => {
try {
const userId = req.user.userId;
const notes = await getNotesByUserId(userId);
res.json({
data: notes
});
} catch (error) {
next(error);
}
});
module.exports = router;
這裡最重要的是:
router.get('/', verifyToken, async (req, res, next) => {
當使用者呼叫:
GET /api/notes
Request 不會直接進入後面的 Route Handler,而是先經過 verifyToken。
如果沒有 Token,或 Token 驗證失敗,Middleware 就會回傳 401,流程停在這裡。
如果驗證成功,Middleware 會把使用者資訊放進 req.user,再呼叫 next(),Route Handler 才會繼續執行。
所以到了這裡:
const userId = req.user.userId;
就能知道目前是哪個使用者,接著再拿 userId 去查詢他的筆記。
這也是 Middleware 很適合處理驗證的原因:需要登入的 API 都可以共用 verifyToken,Controller 只需要專心處理自己的工作。
整個流程其實就是:
Request
↓
verifyToken
↓
檢查 Authorization
↓
取得 Token
↓
jwt.verify()
↓
驗證失敗 → 401
↓
驗證成功
↓
req.user = decoded
↓
next()
↓
Controller
到了這裡,JWT、Middleware 和 API 就真正接起來了。
回頭看一次完整流程。
使用者先登入:
POST /auth/login
↓
驗證帳號密碼
↓
簽發 JWT
↓
前端取得 Token
之後呼叫需要登入的 API:
GET /api/notes
↓
Authorization: Bearer <JWT>
↓
verifyToken
↓
取得 Token
↓
jwt.verify()
↓
驗證成功
↓
req.user = decoded
↓
next()
↓
Controller
↓
req.user.userId
↓
查詢使用者的資料
↓
回傳 Response
如果中間任何一步驗證失敗,就會直接回傳 401,Request 不會進入 Controller。
JWT Middleware 做的事情其實很單純:先確認 Request 有沒有帶 Token,再確認這組 Token 是否有效。驗證成功後,把使用者資訊放進 req.user,讓後面的 Controller 可以直接使用。
這樣一來,需要登入的 API 就不用各自處理 JWT,統一交給 Middleware 驗證即可。Middleware 負責把關 API 的入口,Controller 則接手後面的資料處理。
把這整段記起來就好:
JWT 帶著身分資訊進來,Middleware 負責驗證,
req.user把驗證結果交給後面的 API。
到這裡,使用者的身分已經能被驗證,API 也有了一道基本的入口檢查。接下來把注意力放到資料庫,看看當一次操作需要處理好幾筆資料時,怎麼確保資料不會只完成一半。