You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS 14中间件中如何验证JWT令牌的过期状态?

问题:NextJS 14中间件中验证JWT令牌的正确方式

我用JWT、NextJS和MongoDB开发认证授权应用,已实现/signup和/signin功能,登录时生成1分钟过期的JWT令牌用于测试:

  • 登录后可正常访问/admin、/profile路由,也能获取/profile数据
  • 令牌过期后,无法获取/profile数据,符合预期

但存在问题:令牌过期后,Cookie中仍保留无效的过期令牌,导致我依然能访问所有受保护路由。

我尝试两种方案均失败:

  1. 直接在中间件中使用... && (!token || JSON.parse(token).expiredAt),触发语法错误:SyntaxError: Unexpected token 'e', "eyJhbGciOi"... is not valid JSON
  2. 编写令牌验证函数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模块:

  1. 安装依赖:
npm install jose
# 或
yarn add jose
  1. 中间件中实现验证逻辑:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 18:35:24