NextJS 13.1.1 + NextAuth 4.3.4:middleware.ts中getToken始终返回null
使用NextJS 13.1.1和next-auth 4.3.4,调用getToken({req, secret})始终返回null。已配置process.env.NEXTAUTH_SECRET,Middleware在未认证时能正常重定向到登录页,但无法获取cookie中的令牌。
package.json:
{ "name": "frontend", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "deploy": "npx serverless" }, "dependencies": { "@emotion/react": "^11.10.0", "@emotion/styled": "^11.10.0", "@mui/icons-material": "^5.8.4", "@mui/lab": "^5.0.0-alpha.95", "@mui/material": "^5.10.0", "@reduxjs/toolkit": "^1.8.4", "animate.css": "^4.1.1", "chart.js": "^3.9.1", "cookie": "^0.5.0", "html-to-image": "^1.11.4", "jsonwebtoken": "^9.0.0", "jspdf": "^2.1.0", "moment": "^2.29.4", "next": "^13.1.1", "next-auth": "^4.3.4", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-cookie": "^4.1.1", "react-dom": "^18.2.0", "react-dropzone": "^14.2.2", "react-hook-form": "^7.34.1", "react-redux": "^8.0.2", "sass": "^1.54.4", "swr": "^1.3.0", "uuid": "^8.3.2" }, "devDependencies": { "@types/cookie": "^0.5.1", "@types/jsonwebtoken": "^9.0.0", "@types/node": "18.7.5", "@types/react": "18.0.17", "@types/react-dom": "18.0.6", "@types/uuid": "^8.3.4", "eslint": "8.22.0", "eslint-config-next": "^13.1.1", "typescript": "4.7.4" } }
[...nextauth].js:
import NextAuth, { NextAuthOptions } from "next-auth"; // Providers import Credentials from "next-auth/providers/credentials"; // Services import { authService, refreshAccessToken } from "../../../services"; export const authOptions: NextAuthOptions = { providers: [ Credentials({ name: "Credentials", credentials: { username: { label: "Username", type: "text", placeholder: "C.C.", autoComplete: true, }, password: { label: "Password", type: "password", placeholder: "Type your password...", autoComplete: true, }, }, async authorize(credentials) { return await authService({ username: credentials!.username, password: credentials!.password, }); }, }), ], pages: { signIn: "/auth/login", }, session: { strategy: "jwt", maxAge: 60 * 60 * 7, // 7 hours updateAge: 60 * 60 * 2, }, callbacks: { async jwt({ token, account, user }) { if (user) { token.accessToken = user.accessToken; token.refreshToken = user.refreshToken; if (account) { switch (account.type) { case "credentials": token.user = user.user; token.accessTokenExpires = user.accessTokenExpires; break; } } } if (Date.now() / 1000 < (token.accessTokenExpires as number)) { return token; } return await refreshAccessToken(token); }, async session({ session, token }) { session.accessToken = token.accessToken; session.user = token.user as any; return session; }, }, // secret: process.env.NEXTAUTH_SECRET!, secret: process.env.NEXTAUTH_SECRET, }; export default NextAuth(authOptions);
middleware.ts:
import { getToken } from "next-auth/jwt"; import { withAuth } from "next-auth/middleware"; import { NextResponse } from "next/server"; // Interfaces import { IUser } from "./interfaces"; const secret = process.env.NEXTAUTH_SECRET; export default withAuth(async function middleware(req) { const token = await getToken({ req, secret }); // ALWAYS returns null const { user } = (token as { user: IUser; accessToken: string }) || {}; const isAuth = !!token; const isAuthPage = req.nextUrl.pathname.startsWith("/auth"); if (isAuthPage) { if (isAuth) { // return NextResponse.redirect(new URL(`/${user.hierarchy}`, req.url)); return NextResponse.redirect(new URL(`/role`, req.url)); } return null; } if (!isAuth) { let from = req.nextUrl.pathname; if (req.nextUrl.search) { from += req.nextUrl.search; } return NextResponse.redirect(new URL(`/auth/login`, req.url)); } });
针对你的问题,以下是几个可能的原因和解决步骤:
1. 升级next-auth版本以兼容NextJS 13
next-auth 4.3.4发布于2022年10月,而NextJS 13.1.1是2023年1月的版本,两者存在兼容性问题——早期的next-auth版本对NextJS 13的Middleware API支持不完善,导致无法正确读取cookie中的令牌。
操作:将next-auth升级到至少4.18.0以上的版本(建议最新稳定版):
npm install next-auth@latest
2. 显式配置NextAuth的Cookie参数
默认的Cookie配置可能在NextJS 13的环境下无法被Middleware正确读取,需要在authOptions中添加Cookie配置,确保路径、SameSite等参数正确:
修改[...nextauth].js中的authOptions,添加cookies配置:
export const authOptions: NextAuthOptions = { // ...其他配置 cookies: { sessionToken: { name: `next-auth.session-token`, options: { httpOnly: true, sameSite: "lax", path: "/", secure: process.env.NODE_ENV === "production", }, }, // 若需要其他cookie配置,可参考next-auth文档添加 }, // ...其他配置 };
3. 利用withAuth上下文直接获取Token
使用withAuth包裹Middleware时,可直接通过req.nextauth.token获取令牌,无需再调用getToken,避免手动传递参数出错:
修改middleware.ts:
import { withAuth } from "next-auth/middleware"; import { NextResponse } from "next/server"; // Interfaces import { IUser } from "./interfaces"; export default withAuth(async function middleware(req) { // 直接从withAuth的上下文获取token const token = req.nextauth.token; const { user } = (token as { user: IUser; accessToken: string }) || {}; const isAuth = !!token; const isAuthPage = req.nextUrl.pathname.startsWith("/auth"); if (isAuthPage) { if (isAuth) { return NextResponse.redirect(new URL(`/role`, req.url)); } return null; } if (!isAuth) { let from = req.nextUrl.pathname; if (req.nextUrl.search) { from += req.nextUrl.search; } return NextResponse.redirect(new URL(`/auth/login`, req.url)); } });
4. 验证NEXTAUTH_SECRET的有效性
确保process.env.NEXTAUTH_SECRET在Middleware中能正确读取,且值与[...nextauth].js中使用的一致。如果是Serverless部署,需确认环境变量已正确注入到部署环境中。
可在Middleware中临时添加日志验证:
console.log("NEXTAUTH_SECRET:", secret); // 检查是否输出正确的值
5. 检查authorize返回的用户结构
确保authService返回的用户对象包含accessToken、refreshToken、user、accessTokenExpires字段,且accessTokenExpires是正确的秒级时间戳。如果字段缺失或类型错误,会导致JWT令牌生成异常,最终无法被getToken解析。
内容的提问来源于stack exchange,提问作者wthoutjc

