NextAuth服务端调用signIn报window未定义错误及解决方案咨询
问题解答
核心结论
你不能在服务端直接调用signIn()——这个方法是next-auth/react提供的客户端专属方法,内部依赖浏览器的window对象,服务端环境没有这个对象,所以会抛出ReferenceError: window is not defined错误。
两种可行的优化方案
方案1:注册成功后由客户端触发登录(简单易实现)
API路由只负责完成用户创建,前端收到成功响应后,再调用客户端的signIn()完成会话创建和跳转。
修改后的API路由代码:
import prisma from "@/libs/client"; import bcrypt from "bcrypt"; // 需先安装bcrypt,用于加密密码 export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).json({ error: "Method Not Allowed" }); } const { email, password, confirmPassword } = req.body; if (!email || !password || !confirmPassword) { return res.status(400).json({ error: "表单未填写完整" }); } if (password !== confirmPassword) { return res.status(400).json({ error: "密码不匹配" }); } // 先检查用户是否已存在 const existingUser = await prisma.appUsers.findUnique({ where: { email } }); if (existingUser) { return res.status(409).json({ error: "该邮箱已注册" }); } // 加密密码(必须操作,禁止存储明文密码) const hashedPassword = await bcrypt.hash(password, 10); try { await prisma.appUsers.create({ data: { email, password: hashedPassword, }, }); // 返回成功信息,让前端触发登录流程 return res.status(201).json({ success: true, email }); } catch (error) { return res.status(500).json({ error: "注册失败,请重试" }); } }
前端注册组件示例:
import { useState } from "react"; import { signIn } from "next-auth/react"; export default function RegisterForm() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [confirmPassword, setConfirmPassword] = useState(""); const [error, setError] = useState(""); const handleSubmit = async (e) => { e.preventDefault(); setError(""); const response = await fetch("/api/register", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email, password, confirmPassword }), }); const data = await response.json(); if (!response.ok) { setError(data.error); return; } // 注册成功后调用客户端signIn,完成登录并跳转 const signInResult = await signIn("credentials", { email, password, callbackUrl: "/app/feed", redirect: true, // 自动跳转到目标页面 }); if (signInResult?.error) { setError("登录失败,请重试"); } }; return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" required /> <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" required /> <input type="password" value={confirmPassword} onChange={(e) => setConfirmPassword(e.target.value)} placeholder="确认密码" required /> {error && <p style={{ color: "red" }}>{error}</p>} <button type="submit">注册</button> </form> ); }
方案2:服务端直接创建会话(无需客户端二次请求,更流畅)
利用NextAuth的服务端工具手动创建用户会话,然后直接重定向到目标页面,需要依赖你的NextAuth配置。
修改后的API路由代码:
import prisma from "@/libs/client"; import bcrypt from "bcrypt"; import { authOptions } from "@/pages/api/auth/[...nextauth]"; // 导入你的NextAuth配置 import { createSession } from "next-auth/core"; export default async function handler(req, res) { if (req.method !== "POST") { return res.status(405).json({ error: "Method Not Allowed" }); } const { email, password, confirmPassword } = req.body; if (!email || !password || !confirmPassword) { return res.status(400).json({ error: "表单未填写完整" }); } if (password !== confirmPassword) { return res.status(400).json({ error: "密码不匹配" }); } const existingUser = await prisma.appUsers.findUnique({ where: { email } }); if (existingUser) { return res.status(409).json({ error: "该邮箱已注册" }); } const hashedPassword = await bcrypt.hash(password, 10); const user = await prisma.appUsers.create({ data: { email, password: hashedPassword, }, }); // 手动创建NextAuth会话 const session = await createSession({ session: { user: { id: user.id, email: user.email, // 按需添加其他用户字段,需与NextAuth配置一致 }, expires: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30天有效期 }, secret: authOptions.secret, }); // 设置会话Cookie(需与NextAuth的Cookie配置匹配) res.setHeader( "Set-Cookie", `${authOptions.cookies.sessionToken.name}=${session.sessionToken}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=${30 * 24 * 60 * 60}` ); // 重定向到目标页面 return res.redirect(302, "/app/feed"); }
重要安全提醒
- 禁止存储明文密码:上述代码均使用
bcrypt加密密码,你的原代码直接存储明文密码是严重安全漏洞,必须修复。 - 确保NextAuth的
credentialsprovider验证逻辑使用bcrypt.compare比对密码,示例如下:
// pages/api/auth/[...nextauth].js 中的credentials配置 providers: [ CredentialsProvider({ async authorize(credentials) { const user = await prisma.appUsers.findUnique({ where: { email: credentials.email } }); if (!user) return null; const isPasswordValid = await bcrypt.compare(credentials.password, user.password); return isPasswordValid ? user : null; }, }), ],
内容的提问来源于stack exchange,提问作者cphill
相关产品推荐
相关产品推荐

