如何在NextAuth中分离管理员与用户的认证API路由及会话
解决NextAuth中管理员与普通用户的认证路由分离与会话管理问题
需求背景
基于Next.js搭建的全栈电商系统,包含管理员、普通用户两类角色,数据分别存储在独立MySQL数据表中。使用NextAuth实现身份认证,需要分离两类用户的认证API路由:管理员对应/api/admin(由NextAuth路由处理登录逻辑)、普通用户对应/api/user,同时解决当前管理员认证的错误问题。
解决方案步骤
1. 创建分离的NextAuth API路由
在pages/api目录下分别创建两类用户的NextAuth路由文件:
- 管理员路由:
pages/api/admin/[...nextauth].js - 普通用户路由:
pages/api/user/[...nextauth].js
修改管理员路由配置(添加角色标识与会话回调)
import NextAuth from "next-auth/next"; import CredentialsProvider from "next-auth/providers/credentials"; import Admin from "../../../../models/admin"; import { verifyPassword } from "../../../../utils/hash-p"; export default NextAuth({ secret: process.env.NEXTAUTH_SECRET, // 必须配置,从环境变量读取 session: { jwt: true, }, providers: [ CredentialsProvider({ async authorize(credentials) { try { const admin = await Admin.findOne({ where: { email: credentials.email }, }); if (!admin) throw new Error("管理员不存在"); const isValid = await verifyPassword( credentials.password, admin.password ); if (!isValid) throw new Error("密码错误"); // 返回包含角色的用户信息,用于后续会话标识 return { email: admin.email, role: "admin" }; } catch (error) { throw new Error(error.message); } }, }), ], // 添加回调函数,将角色信息存入JWT和会话 callbacks: { async jwt({ token, user }) { if (user) { token.role = user.role; } return token; }, async session({ session, token }) { if (session.user) { session.user.role = token.role; } return session; }, }, // 自定义登录页面跳转 pages: { signIn: "/admin/login", }, });
普通用户路由示例(类似结构)
import NextAuth from "next-auth/next"; import CredentialsProvider from "next-auth/providers/credentials"; import User from "../../../../models/user"; import { verifyPassword } from "../../../../utils/hash-p"; export default NextAuth({ secret: process.env.NEXTAUTH_SECRET, session: { jwt: true, }, providers: [ CredentialsProvider({ async authorize(credentials) { try { const user = await User.findOne({ where: { email: credentials.email }, }); if (!user) throw new Error("用户不存在"); const isValid = await verifyPassword( credentials.password, user.password ); if (!isValid) throw new Error("密码错误"); return { email: user.email, role: "user" }; } catch (error) { throw new Error(error.message); } }, }), ], callbacks: { async jwt({ token, user }) { if (user) token.role = user.role; return token; }, async session({ session, token }) { if (session.user) session.user.role = token.role; return session; }, }, pages: { signIn: "/user/login", }, });
2. 客户端登录调用指定路由
修改管理员登录页面的signIn调用,指定使用管理员专属的NextAuth路由:
import { useRef } from "react"; import Image from "next/image"; import { signIn } from "next-auth/react"; const AdminLogin = () => { const handleRequest = async (e) => { e.preventDefault(); const email = emailRef.current.value; const password = passRef.current.value; const result = await signIn("credentials", { redirect: false, email, password, auth: "/api/admin", // 指定管理员路由前缀 callbackUrl: "/admin/dashboard", // 登录成功后跳转页面 }); console.log(result); // 处理登录错误提示 if (result.error) { alert(result.error); } }; const emailRef = useRef(); const passRef = useRef(); return ( <main className="form-signin w-100 m-auto"> <div className="row justify-content-center align-items-center" style={{ height: "100vh" }} > <div className="col-sm-3 col-11"> <form onSubmit={handleRequest}> <div className="position-relative text-center" style={{ height: "150px" }} > <Image className="mb-4" src="/assets/images/logo.png" width={100} height={143} alt="benizh" unoptimized /> </div> <h1 className="h3 mb-3 fw-normal text-center">ورود ادمین</h1> <div className="form-floating mb-3"> <input type="email" className="form-control text-end" id="floatingInput" placeholder="name@example.com" style={{ direction: "ltr" }} ref={emailRef} required /> <label htmlFor="floatingInput">پست الکترونیک</label> </div> <div className="form-floating mb-3"> <input type="password" className="form-control text-end" id="floatingPassword" placeholder="Password" style={{ direction: "ltr" }} ref={passRef} required /> <label htmlFor="floatingPassword">رمز عبور</label> </div> <button className="w-100 btn btn-lg btn-primary" type="submit"> ورود </button> </form> </div> </div> </main> ); }; export default AdminLogin;
3. 会话权限校验
在需要权限的页面或API路由中,通过会话信息区分角色:
// 前端页面中使用useSession获取角色 import { useSession } from "next-auth/react"; const AdminDashboard = () => { const { data: session, status } = useSession(); if (status === "loading") return <div>加载中...</div>; if (!session || session.user.role !== "admin") { return <div>无权访问此页面</div>; } return <div>管理员仪表盘</div>; }; export default AdminDashboard;
4. 常见错误排查
- 环境变量缺失:确保
.env文件中配置了NEXTAUTH_SECRET(可通过openssl rand -hex 32生成) - 数据库查询失败:检查Sequelize模型定义、数据库连接字符串是否正确,确保Admin表存在且字段匹配
- 密码验证逻辑错误:确认
verifyPassword函数正确使用哈希对比(如bcrypt的compare方法) - 路由路径错误:确保
signIn的auth参数与NextAuth路由文件路径一致(如管理员路由是/api/admin/[...nextauth].js,则auth值为/api/admin)
内容的提问来源于stack exchange,提问作者Amir Hossein
相关产品推荐
相关产品推荐

