验证 Next.js 中间件中的 JWT 令牌会抛出“找不到模块:无法解析‘加密’”错误

Phi*_* D. 3 middleware node.js jwt next.js

我正在尝试使用 JWT 验证通过中间件进行身份验证,但不幸的是,我遇到了一些无法找到解决方案的错误。

./node_modules/jwa/index.js:3:0
Module not found: Can't resolve 'crypto'

Import trace for requested module:
./node_modules/jws/lib/sign-stream.js
./node_modules/jws/index.js
./node_modules/jsonwebtoken/verify.js
./middleware.ts

https://nextjs.org/docs/messages/module-not-found

You're using a Node.js module (crypto) which is not supported in the Edge Runtime.
Learn more: https://nextjs.org/docs/api-reference/edge-runtime
Run Code Online (Sandbox Code Playgroud)

包.json

{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@emotion/react": "^11.9.3",
    "@emotion/styled": "^11.9.3",
    "@mui/material": "^5.8.6",
    "@prisma/client": "^4.0.0",
    "axios": "^0.27.2",
    "buffer": "^6.0.3",
    "cookie": "^0.5.0",
    "jsonwebtoken": "^8.5.1",
    "next": "12.2.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hook-form": "^7.33.0"
  },
  "devDependencies": {
    "@types/node": "18.0.0",
    "@types/react": "18.0.14",
    "@types/react-dom": "18.0.5",
    "eslint": "8.18.0",
    "eslint-config-next": "12.2.0",
    "prisma": "^4.0.0",
    "typescript": "4.7.4"
  }
}
Run Code Online (Sandbox Code Playgroud)

中间件.ts

import { NextResponse } from "next/server";
import verify from "jsonwebtoken/verify";
import { urlToHttpOptions } from "url";
import type { NextRequest } from 'next/server'

const secret = process.env.SECRET;

export default function middleware(req: NextRequest) {
    const { cookies } = req;
    const { search, protocol, host } = req.nextUrl

    const jwt = cookies.OutsiteJWT;
    const url = req.url;

    if (url.includes('/dashboard')) {
        if (jwt === undefined) {
            return NextResponse.redirect("http://localhost:3000/login");
        }

        try {
            verify(jwt, secret); // <---- ERROR COMES FROM HERE
            return NextResponse.next();
        } catch (error) {
            return NextResponse.redirect("/login");
        }
    }

    return NextResponse.next();
}
Run Code Online (Sandbox Code Playgroud)

/pages/api/auth/login.ts

import { NextResponse } from "next/server";
import verify from "jsonwebtoken/verify";
import { urlToHttpOptions } from "url";
import type { NextRequest } from 'next/server'

const secret = process.env.SECRET;

export default function middleware(req: NextRequest) {
    const { cookies } = req;
    const { search, protocol, host } = req.nextUrl

    const jwt = cookies.OutsiteJWT;
    const url = req.url;

    if (url.includes('/dashboard')) {
        if (jwt === undefined) {
            return NextResponse.redirect("http://localhost:3000/login");
        }

        try {
            verify(jwt, secret); // <---- ERROR COMES FROM HERE
            return NextResponse.next();
        } catch (error) {
            return NextResponse.redirect("/login");
        }
    }

    return NextResponse.next();
}
Run Code Online (Sandbox Code Playgroud)

我尝试注释掉try/catch来检查令牌是否已设置,并且它工作正常,但是当我尝试在中间件中验证时,它失败了。

在版本 12.2.0 中,中间件很稳定,但也有一些变化。

其他人是否遇到类似的问题或知道如何解决这个问题?

Phi*_* D. 6

根据与 GitHub 中用户的讨论,显然jose库在中间件中运行 Edge 函数时效果更好,而jsonwebtoken则不然。

/中间件.ts

import { NextResponse } from "next/server";
import { urlToHttpOptions } from "url";
import type { NextRequest } from 'next/server'
import { verify } from "./services/jwt_sign_verify";

const secret = process.env.SECRET || "secret";

export default async function middleware(req: NextRequest) {
  const jwt = req.cookies.get("OutsiteJWT");
  const url = req.url;
  const {pathname} = req.nextUrl;

  if (pathname.startsWith("/dashboard")) {
    if (jwt === undefined) {
      req.nextUrl.pathname = "/login";
      return NextResponse.redirect(req.nextUrl);
    }

    try {
      await verify(jwt, secret);
      return NextResponse.next();
    } catch (error) {
      req.nextUrl.pathname = "/login";
      return NextResponse.redirect(req.nextUrl);
    }
  }

  return NextResponse.next();
}
Run Code Online (Sandbox Code Playgroud)

/services/jwt_sign_verify.ts

import { SignJWT, jwtVerify, type JWTPayload } from 'jose';
import { Token } from "@typescript-eslint/types/dist/generated/ast-spec";

export async function sign(payload: string, secret: string): Promise<string> {
    const iat = Math.floor(Date.now() / 1000);
    const exp = iat + 60 * 60; // one hour

    return new SignJWT({ payload })
        .setProtectedHeader({ alg: 'HS256', typ: 'JWT' })
        .setExpirationTime(exp)
        .setIssuedAt(iat)
        .setNotBefore(iat)
        .sign(new TextEncoder().encode(secret));
}

export async function verify(token: string, secret: string): Promise<JWTPayload> {
    const { payload } = await jwtVerify(token, new TextEncoder().encode(secret));
    // run some checks on the returned payload, perhaps you expect some specific values

    // if its all good, return it, or perhaps just return a boolean
    return payload;
}
Run Code Online (Sandbox Code Playgroud)

/pages/api/auth/login.ts

/* eslint-disable import/no-anonymous-default-export */
import { serialize } from "cookie";
import { sign } from "../../../services/jwt_sign_verify";

const secret = process.env.SECRET || "secret";

export default async function (req, res) {
    const { username, password } = req.body;

    // Check in the database
    if (username === "Admin" && password === "Admin") {
        const token = await sign(
            "testing",
            secret
        );

        const serialised = serialize("OursiteJWT", token, {
            httpOnly: true,
            secure: process.env.NODE_ENV !== "development",
            sameSite: "strict",
            maxAge: 60 * 60 * 24 * 30,
            path: "/",
        });

        res.setHeader("Set-Cookie", serialised);

        res.status(200).json({ message: "Success!" });
    } else {
        res.status(401).json({ message: "Invalid credentials!" });
    }
}
Run Code Online (Sandbox Code Playgroud)