Next.js中间件无法获取fetch API返回JSON数据问题求助
问题:Next.js中间件调用/api/me接口返回未授权
我用Next.js中间件实现基于JWT的角色认证,用户登录后生成JWT令牌,通过http://localhost:3000/api/me接口获取用户数据:
- 有令牌时返回用户ID和角色,示例:
{"user":{"id":"649eaab9129b600eaccd800a","role":"admin"}} - 无令牌时返回未授权信息:
{"message":"Unauthorized"}
浏览器访问该API能正常获取数据,但中间件里用fetch请求这个接口时,返回未授权信息。
相关代码如下:
package.json
{ "name": "role-based-authentication-with-jwt", "version": "0.1.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" }, "dependencies": { "@types/node": "20.4.1", "@types/react": "18.2.14", "@types/react-dom": "18.2.6", "autoprefixer": "10.4.14", "jsonwebtoken": "^9.0.1", "mongodb": "^5.7.0", "mongoose": "^7.3.2", "next": "13.4.9", "postcss": "8.4.25", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.45.1", "react-toastify": "^9.1.3", "tailwindcss": "3.3.2", "typescript": "5.1.6" }, "devDependencies": { "@types/jsonwebtoken": "^9.0.2" } }
middleware.ts
import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export async function middleware(request: NextRequest) { const token = request.cookies.get("token")?.value; const response = await fetch("http://localhost:3000/api/me"); const { user } = await response.json(); if (!token && !user?.role) { if (!request.nextUrl.pathname.startsWith("/login")) { return NextResponse.redirect(new URL("/login", request.url)); } } else if ( token && user?.role && !request.nextUrl.pathname.startsWith(`/${user?.role}`) && !request.nextUrl.pathname.startsWith("/login") ) { return NextResponse.redirect(new URL("/access", request.url)); } else { if (!request.nextUrl.pathname.startsWith(`/${user?.role}`)) { return NextResponse.redirect(new URL(`/${user?.role}`, request.url)); } } } export const config = { matcher: ["/admin/:path*", "/superadmin/:path*", "/user/:path*", "/login"], };
api/me/route.ts
import { getUser } from "@/helpers/getUser"; import { NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { try { const token = request.cookies.get("token")?.value || ""; if (!token) { return NextResponse.json( { message: "Unauthorized", }, { status: 401, } ); } const user = getUser(request); return NextResponse.json({ user, }); } catch (error: any) { return NextResponse.json({ error: error.message }, { status: 500 }); } }
helpers/getUser.ts
import { NextRequest } from "next/server"; import { verify } from "jsonwebtoken"; export const getUser = (request: NextRequest): TokenData => { try { const token = request.cookies.get("token")?.value || ""; const decodedToken: any = verify(token, process.env.TOKEN_SECRET!); return { id: decodedToken.id, role: decodedToken.role }; } catch (error: any) { throw new Error(error.message); } };
需要解决:如何在中间件中通过fetch API获取用户角色,完成角色路由控制?
解决方案
问题核心是中间件中的fetch请求未携带用户cookie:浏览器发起请求时会自动带上当前域名的cookie,但中间件里的fetch是服务端发起的请求,默认不会携带任何cookie,导致/api/me接口拿不到token,返回未授权。
方案1:直接在中间件中解析JWT(推荐)
没必要通过fetch调用/api/me接口,直接复用getUser工具函数在中间件里解析token获取角色,减少一次HTTP请求,性能更优:
修改后的middleware.ts:
import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { getUser } from "@/helpers/getUser"; export async function middleware(request: NextRequest) { let user = null; const token = request.cookies.get("token")?.value; try { if (token) { user = getUser(request); } } catch (err) { // 解析token失败,视为无有效用户 user = null; } if (!token && !user?.role) { if (!request.nextUrl.pathname.startsWith("/login")) { return NextResponse.redirect(new URL("/login", request.url)); } } else if ( token && user?.role && !request.nextUrl.pathname.startsWith(`/${user.role}`) && !request.nextUrl.pathname.startsWith("/login") ) { return NextResponse.redirect(new URL("/access", request.url)); } else if (user?.role && !request.nextUrl.pathname.startsWith(`/${user.role}`)) { return NextResponse.redirect(new URL(`/${user.role}`, request.url)); } } export const config = { matcher: ["/admin/:path*", "/superadmin/:path*", "/user/:path*", "/login"], };
方案2:让fetch请求携带cookie
如果一定要通过调用/api/me接口获取用户信息,需要在fetch时手动传递cookie:
修改中间件中的fetch部分:
const response = await fetch("http://localhost:3000/api/me", { headers: { Cookie: request.headers.get("Cookie") || "" } });
这样服务端发起的fetch请求就会携带用户的cookie,/api/me接口就能正常解析token返回用户数据。
注意:这种方式会多一次内部HTTP请求,性能不如方案1,更推荐直接解析token。
内容的提问来源于stack exchange,提问作者Vijay
相关产品推荐
相关产品推荐

