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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 19:02:09