最近在做一個 Side Project 時,我想保護部分頁面(像是 /dashboard)需要登入才能訪問,於是決定使用 Next.js 的 Middleware 功能來進行 JWT 身份驗證。
理論上應該很簡單——從 Cookie 抓 token,用 jsonwebtoken 驗證它。但很快我就遇到阻礙:在 Middleware 中用 verify() 解 token,竟然直接報錯,完全無法運行。
為什麼 jsonwebtoken 在 Middleware 裡失敗?
當你試圖這樣寫時:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { NextResponse } from 'next/server'; import { verify } from 'jsonwebtoken';
export function middleware(request) { const token = request.cookies.get('token')?.value;
try { const decoded = verify(token, process.env.JWT_SECRET); return NextResponse.next(); } catch (error) { return NextResponse.redirect(new URL('/login', request.url)); } }
|
會出現這個錯誤訊息:
1
| Error: The Edge Runtime does not support Node.js 'crypto' module.
|
這代表你不能在 Middleware 中使用 Node.js 的某些套件,像是 crypto。
Edge Runtime 是什麼?
Next.js 的 Middleware 是在 Edge Runtime 上執行的,這個環境接近於瀏覽器,不具備完整的 Node.js API。所以 jsonwebtoken 這種依賴 Node.js crypto 模組的函式庫在這裡無法運作。
解法一:使用 jose 函式庫 (採用方式)
jose 是專門設計來兼容 Web Crypto API 的 JWT 函式庫,可以在 Edge Runtime 中順利運作。
Middleware 驗證範例:
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
| import { NextResponse } from 'next/server'; import { jwtVerify } from 'jose';
export async function middleware(request) { const token = request.cookies.get('token')?.value;
if (!token) { return NextResponse.redirect(new URL('/login', request.url)); }
try { const secret = new TextEncoder().encode(process.env.JWT_SECRET); const { payload } = await jwtVerify(token, secret);
const requestHeaders = new Headers(request.headers); requestHeaders.set('x-user-id', payload.sub);
return NextResponse.next({ request: { headers: requestHeaders, }, }); } catch (error) { return NextResponse.redirect(new URL('/login', request.url)); } }
export const config = { matcher: ['/dashboard/:path*', '/api/protected/:path*'], };
|
- 無需額外 API 呼叫
- jose 是針對 Web 環境優化的函式庫,效能好、兼容性強
解法二:將驗證邏輯移至 API Route
如果你已經深度依賴 jsonwebtoken,可以將 JWT 驗證邏輯放到 API Route,由 Middleware 呼叫。
1 2 3 4 5 6 7 8 9 10 11 12
| import { verify } from 'jsonwebtoken';
export default function handler(req, res) { try { const token = req.body.token; const decoded = verify(token, process.env.JWT_SECRET); res.status(200).json({ valid: true, user: decoded }); } catch (error) { res.status(401).json({ valid: false, error: error.message }); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { NextResponse } from 'next/server';
export async function middleware(request) { const token = request.cookies.get('token')?.value;
if (!token) { return NextResponse.redirect(new URL('/login', request.url)); }
const response = await fetch(`${request.nextUrl.origin}/api/auth/verify`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token }), });
const result = await response.json();
if (!result.valid) { return NextResponse.redirect(new URL('/login', request.url)); }
return NextResponse.next(); }
|
- 增加一次內部 API 呼叫
- 較影響效能與伺服器負載
使用 Web Crypto API
進階使用者可以直接用 Web Crypto API 實作 JWT 驗證(這是 jose 底層的做法),但需要處理 Base64 解碼、簽名驗證、payload 檢查等細節。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { NextResponse } from 'next/server';
async function verifyJWT(token, secret) { const parts = token.split('.'); if (parts.length !== 3) throw new Error('Invalid token format'); return { }; }
export async function middleware(request) { }
|
小結:
在 Next.js Middleware 中處理身份驗證需要注意 Edge Runtime 的限制。使用 jose 函式庫是最直接的解決方案,它專為 Web 環境設計,能在 Edge Runtime 中正常工作。