前言
在 Express.js 認證系統中,常見的做法是在每個需要驗證的路由中直接解析 JWT token。
但有一個更好的實作方式:使用中介軟體將認證結果存放在 res.locals 中。
這不僅是官方建議的做法,也避免了重複解析 token 的效能問題。
TL;DR
使用 res.locals 處理認證是 Express.js 的標準實作:
- 避免在每個路由重複解析 JWT token
- Auth.js、Passport.js 等主流函式庫都採用這種模式
- Express.js 官方文檔明確支持這種用法
直接解析 JWT 的缺點
重複工作的效能問題
如果每個路由都直接解析 JWT,會造成不必要的重複運算:
1 2 3 4 5 6 7 8 9 10 11 12
| app.get('/profile', (req, res) => { const token = req.headers.authorization?.split(' ')[1] const user = jwt.verify(token, JWT_SECRET) res.json({ user }) })
app.get('/orders', (req, res) => { const token = req.headers.authorization?.split(' ')[1] const user = jwt.verify(token, JWT_SECRET) res.json({ orders: getUserOrders(user.id) }) })
|
程式碼重複與維護困難
每個需要認證的路由都要寫類似的 token 驗證邏輯,違反了 DRY 原則,也增加了維護成本。
錯誤處理不一致
不同路由可能有不同的 token 驗證錯誤處理方式,造成 API 回應不一致。
什麼是 res.locals?
根據 Express.js 官方文檔,res.locals 是一個物件,用來存放請求範圍內的區域變數,這些變數只在當前請求-回應週期中可用。
1 2 3 4 5 6
| app.use((req, res, next) => { res.locals.user = req.user res.locals.authenticated = !req.user next() })
|
主流函式庫的標準實作
Auth.js 官方範例
Auth.js 官方文檔明確展示了使用 res.locals 的標準模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { getSession } from "@auth/express"
export function authSession(req, res, next) { res.locals.session = await getSession(req) next() }
app.use(authSession)
app.get("/", (req, res) => { const { session } = res.locals res.render("index", { user: session?.user }) })
|
Passport.js 的實作模式
Passport.js 在認證成功時會設置 req.user 屬性,許多開發者會將此資訊複製到 res.locals 以便在視圖中使用:
1 2 3 4 5 6
| app.use((req, res, next) => { res.locals.user = req.isAuthenticated() ? req.user : null res.locals.isAuthenticated = req.isAuthenticated() next() })
|
為什麼這是好實作?
1. 官方支持的標準
Express.js 官方文檔說明 res.locals 就是用來存放請求級別的資訊,如認證用戶、用戶設定等。這不是 hack 或 workaround,而是設計用途。
2. 生態系統共識
主流的認證函式庫都採用類似模式:
- Auth.js 直接在文檔中展示
res.locals.session
- Passport.js 社群普遍使用
res.locals.user
- 許多教學都展示將認證狀態存放在
res.locals 的模式
3. 分離關注點
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| function authMiddleware(req, res, next) { const token = req.headers.authorization?.split(' ')[1] if (token) { const decoded = jwt.verify(token, JWT_SECRET) res.locals.user = decoded } next() }
app.get('/profile', authMiddleware, (req, res) => { const { user } = res.locals if (!user) { return res.status(401).json({ error: 'Unauthorized' }) } res.json({ profile: user }) })
|
4. 視圖整合優勢
在使用模板引擎時,res.locals 的資料會自動傳遞給視圖:
1 2 3 4 5 6 7 8
| app.use((req, res, next) => { res.locals.currentUser = req.user next() })
|
常見的反模式
避免的做法
有些開發者認為過度使用 res.locals 會讓除錯變困難,但這通常是因為:
- 濫用中介軟體模式 - 把業務邏輯也放在中介軟體裡
- 過度耦合 - 多個中介軟體互相依賴
res.locals 的順序
正確的使用原則
中介軟體應該用於所有 HTTP 請求共通的事項,且不包含業務邏輯:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function authenticate(req, res, next) { const token = getTokenFromRequest(req) const user = validateToken(token) res.locals.user = user next() }
function requireAuth(req, res, next) { if (!res.locals.user) { return res.status(401).json({ error: 'Unauthorized' }) } next() }
function badMiddleware(req, res, next) { const user = res.locals.user const orders = getUserOrders(user.id) res.locals.orders = orders next() }
|
實務上的最佳實作
標準認證中介軟體
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| const jwt = require('jsonwebtoken')
function authMiddleware(req, res, next) { try { const token = req.headers.authorization?.replace('Bearer ', '') if (token) { const decoded = jwt.verify(token, process.env.JWT_SECRET) res.locals.user = decoded res.locals.isAuthenticated = true } else { res.locals.user = null res.locals.isAuthenticated = false } next() } catch (error) { res.locals.user = null res.locals.isAuthenticated = false next() } }
function requireAuth(req, res, next) { if (!res.locals.isAuthenticated) { return res.status(401).json({ error: 'Authentication required' }) } next() }
app.use(authMiddleware) app.get('/profile', requireAuth, (req, res) => { const { user } = res.locals res.json({ user }) })
|
與 req 自定義屬性的比較
雖然也可以使用 req.user 或 req.data 等自定義屬性,但 res.locals 有幾個優勢:
- 語意清晰 - 明確表示這是給視圖用的資料
- 自動傳遞 - 模板引擎會自動取用
res.locals 的資料
- 標準化 - 社群共識,維護性更好
結論
res.locals 在認證系統中的使用確實是標準實作:
- 官方支持 - Express.js 和 Auth.js 官方文檔都展示這種用法
- 生態系統標準 - Passport.js 等主流函式庫採用相同模式
- 效能考量 - 避免重複解析 JWT 或查詢資料庫
- 開發體驗 - 控制器可以直接存取用戶資訊
所以下次有人說使用 res.locals 不是好實作時,可以拿出這些官方文檔來證明這確實是被廣泛接受的標準做法。
參考資料
(fin)