NextJS 14中间件中如何验证JWT令牌的过期状态?
问题:NextJS 14中间件中验证JWT令牌的正确方式
我用JWT、NextJS和MongoDB开发认证授权应用,已实现/signup和/signin功能,登录时生成1分钟过期的JWT令牌用于测试:
- 登录后可正常访问
/admin、/profile路由,也能获取/profile数据 - 令牌过期后,无法获取
/profile数据,符合预期
但存在问题:令牌过期后,Cookie中仍保留无效的过期令牌,导致我依然能访问所有受保护路由。
我尝试两种方案均失败:
- 直接在中间件中使用
... && (!token || JSON.parse(token).expiredAt),触发语法错误:SyntaxError: Unexpected token 'e', "eyJhbGciOi"... is not valid JSON - 编写令牌验证函数
isTokenExpires并在中间件中调用,触发服务器错误:Server Error: The edge runtime does not support Node.js 'crypto' module.
我的中间件代码如下:
import { NextRequest, NextResponse } from "next/server"; import { isTokenExpires } from "./app/_utils/getDataOfToken"; export function middleware(req: NextRequest) { const path = req.nextUrl.pathname; const isAuth = ["/signin", "/signup"]; const isProtected = ["/profile", "/admin", "/sales", "/cart"]; const token = req.cookies.get("token")?.value; if (isProtected.includes(path) && (!token || isTokenExpires(token))) { const response = NextResponse.redirect(new URL("/signin", req.url)); return response; } } export const config = { matcher: ["/", "/signup", "/signin", "/profile", "/admin", "/sales", "/cart"], };
请问如何在NextJS 14的中间件中正确验证JWT令牌?
解决方案
1. 错误原因梳理
- JWT是Base64编码字符串,并非JSON格式,直接
JSON.parse(token)必然报错 - NextJS中间件默认运行在Edge Runtime,不支持Node.js原生
crypto模块,因此依赖该模块的JWT库(如jsonwebtoken)无法使用
2. 可行方案
方案A:手动解析JWT过期时间(无额外依赖)
JWT载荷中包含exp字段(秒级时间戳),可手动解码载荷部分判断是否过期:
import { NextRequest, NextResponse } from "next/server"; // 手动解析JWT载荷,判断令牌是否过期 function isTokenExpired(token: string): boolean { try { // 拆分JWT的头部、载荷、签名 const [, payload] = token.split('.'); // 处理URL安全的Base64编码,解码后转为JSON const decodedPayload = atob(payload.replace(/-/g, '+').replace(/_/g, '/')); const payloadObj = JSON.parse(decodedPayload); // 当前时间戳(秒级)与exp字段对比 const currentTime = Math.floor(Date.now() / 1000); return payloadObj.exp < currentTime; } catch (err) { // 解析失败则视为令牌无效 return true; } } export function middleware(req: NextRequest) { const path = req.nextUrl.pathname; const isProtected = ["/profile", "/admin", "/sales", "/cart"]; const token = req.cookies.get("token")?.value; if (isProtected.includes(path)) { if (!token || isTokenExpired(token)) { return NextResponse.redirect(new URL("/signin", req.url)); } } } export const config = { matcher: ["/", "/signup", "/signin", "/profile", "/admin", "/sales", "/cart"], };
方案B:使用支持Edge Runtime的JWT库
推荐使用jose库,它完全适配Edge环境,无需依赖Node.jscrypto模块:
- 安装依赖:
npm install jose # 或 yarn add jose
- 中间件中实现验证逻辑:
import { NextRequest, NextResponse } from "next/server"; import { jwtVerify } from "jose"; // 与生成令牌时一致的密钥,需从环境变量读取 const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET); export async function middleware(req: NextRequest) { const path = req.nextUrl.pathname; const isProtected = ["/profile", "/admin", "/sales", "/cart"]; const token = req.cookies.get("token")?.value; if (isProtected.includes(path)) { if (!token) { return NextResponse.redirect(new URL("/signin", req.url)); } try { // 验证令牌签名与过期时间 await jwtVerify(token, JWT_SECRET); } catch (err) { // 验证失败(过期/无效)则重定向到登录页 return NextResponse.redirect(new URL("/signin", req.url)); } } } export const config = { matcher: ["/", "/signup", "/signin", "/profile", "/admin", "/sales", "/cart"], };
额外优化建议
- 将受保护路由列表、JWT密钥等配置存入环境变量,便于维护
- 可添加登录/注册页的反向拦截:若存在有效令牌,访问
/signin//signup时自动重定向到首页或/profile
内容的提问来源于stack exchange,提问作者mrhrifat
相关产品推荐
相关产品推荐

