Next.js中如何用NextAuth实现前后端通用的Admin API权限控制
Next.js API路由保护与NextAuth Credentials Provider问题解决
问题梳理
- 需要为Admin API路由实现客户端+服务端双端生效的权限控制,仅允许已验证的管理员访问
- 使用
getServerSession时发现仅客户端生效,不符合需求 - 当前中间件权限控制代码在服务端及服务组件中失效
- NextAuth Credentials Provider无法获取access token
一、通用API路由保护方案(双端生效)
1. API路由内的服务端校验(核心)
不管客户端是否做校验,API路由本身必须做服务端校验,避免客户端绕过。getServerSession在服务端环境(API路由、服务组件)中完全有效,之前的问题大概率是未正确传入NextAuth配置:
// app/api/admin/[...path]/route.ts 或 pages/api/admin/[...path].ts import { getServerSession } from "next-auth/next"; import { authOptions } from "../auth/[...nextauth]"; // 引入你的NextAuth配置 import { NextResponse } from "next/server"; export async function GET(req: Request) { const session = await getServerSession(authOptions); // 三重校验:已登录、是管理员、账号已验证 if (!session?.user || session.user.role !== "ADMIN" || !session.user.isVerified) { return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); } // 正常处理管理员请求 return NextResponse.json({ data: "Admin-only content" }); }
2. 中间件优化(全局拦截)
你之前的中间件代码问题在于req.nextauth.token的获取逻辑,NextAuth v4+的中间件需用getToken方法正确获取JWT token:
// middleware.ts import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; import { getToken } from "next-auth/jwt"; export async function middleware(req: NextRequest) { const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); // 仅拦截/admin开头的API路由 if (req.nextUrl.pathname.startsWith("/api/admin")) { if (!token || token.role !== "ADMIN" || !token.isVerified) { return NextResponse.json({ message: "Unauthorized" }, { status: 401 }); } } return NextResponse.next(); } // 指定中间件生效路径 export const config = { matcher: ["/api/admin/:path*"], };
二、Credentials Provider获取access token解决方案
Credentials Provider需依赖JWT会话策略,手动在回调中传递access token:
// app/api/auth/[...nextauth]/route.ts 或 pages/api/auth/[...nextauth].ts import NextAuth from "next-auth"; import CredentialsProvider from "next-auth/providers/credentials"; export const authOptions = { secret: process.env.NEXTAUTH_SECRET, session: { strategy: "jwt", // 必须启用JWT策略 }, providers: [ CredentialsProvider({ name: "Credentials", credentials: { email: { label: "Email", type: "email" }, password: { label: "Password", type: "password" }, }, async authorize(credentials) { // 这里替换为你的账号校验逻辑(比如调用数据库或后端接口) const user = await fetch("/api/internal/login", { method: "POST", body: JSON.stringify(credentials), headers: { "Content-Type": "application/json" }, }).then(res => res.json()); // 校验通过后返回包含accessToken的用户对象 if (user?.id && user?.role && user?.isVerified && user?.accessToken) { return { id: user.id, email: user.email, role: user.role, isVerified: user.isVerified, accessToken: user.accessToken, }; } return null; }, }), ], callbacks: { async jwt({ token, user }) { // 登录时将用户信息合并到JWT token if (user) { token.accessToken = user.accessToken; token.role = user.role; token.isVerified = user.isVerified; } return token; }, async session({ session, token }) { // 将JWT中的信息同步到session,方便双端获取 session.user.role = token.role; session.user.isVerified = token.isVerified; session.user.accessToken = token.accessToken; return session; }, }, }; export default NextAuth(authOptions);
三、客户端校验补充(提升体验)
在客户端组件调用Admin API前先做校验,提前拦截无权限请求:
// 客户端组件 import { useSession } from "next-auth/react"; import { useEffect, useState } from "react"; export default function AdminDashboard() { const { data: session } = useSession(); const [adminData, setAdminData] = useState(null); useEffect(() => { const fetchAdminData = async () => { if (!session?.user || session.user.role !== "ADMIN" || !session.user.isVerified) { alert("无管理员访问权限"); return; } const res = await fetch("/api/admin/dashboard"); if (res.ok) { setAdminData(await res.json()); } else { alert("请求被拒绝"); } }; fetchAdminData(); }, [session]); return <div>{adminData ? JSON.stringify(adminData) : "加载中..."}</div>; }
内容的提问来源于stack exchange,提问作者Krishna Rathore
相关产品推荐
相关产品推荐

