使用NextAuth CredentialsProvider时Session回调未触发的问题
问题原因
当你设置session: { strategy: "database" }时,NextAuth的数据库适配器仅会为OAuth/Email这类有状态认证提供者自动创建会话并触发session回调,但CredentialsProvider是无状态认证方式,默认不会触发数据库会话的创建流程,这就是为什么你能看到authorize和jwt的日志,却看不到session回调日志、数据库也没有会话记录的原因。而Google登录属于OAuth提供者,适配器会自动处理会话创建,因此session回调正常触发。
解决方案
推荐采用JWT会话策略(这是CredentialsProvider的常规搭配,配置简单且符合无状态认证的设计),具体调整如下:
1. 修改会话策略与回调逻辑
import NextAuth, { type NextAuthOptions } from "next-auth"; import GoogleProvider from "next-auth/providers/google"; import CredentialsProvider from "next-auth/providers/credentials"; import bcryptjs from "bcryptjs"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { env } from "../../../env/server.mjs"; import { prisma } from "../../../server/db/client"; export const authOptions: NextAuthOptions = { callbacks: { async session({ session, token }) { console.log('session ', session) console.log('token ', token) // 从JWT中提取自定义用户信息,注入到会话中 if (session.user) { session.user.id = token.id as string; session.user.email = token.email as string; session.user.isRegistered = token.isRegistered as boolean; } return session; }, async jwt({ token, user }) { console.log('user is ', user) console.log('token is ', token) // 登录时将authorize返回的用户信息存入JWT if (user) { token.id = user.id; token.email = user.email; token.isRegistered = user.isRegistered; } return token; } }, adapter: PrismaAdapter(prisma), providers: [ GoogleProvider({ clientId: env.GOOGLE_CLIENT_ID, clientSecret: env.GOOGLE_CLIENT_SECRET, }), CredentialsProvider({ name: "credentials", credentials: { email: { label: 'Email', name: 'email', type: 'email' }, password: { label: 'Password', name: 'password', type: 'password' } }, async authorize(credentials) { if(!credentials) return null; const { email, password } = credentials; if(!email || !password) return null; const userExists = await prisma.user.findUnique({ where: { email: credentials.email } }); if (!userExists || !userExists.hashedPassword) return null; const isPasswordValid = await bcryptjs.compare(password, userExists.hashedPassword); if(!isPasswordValid) return null; // 返回自定义用户对象(无需修改,保持原逻辑即可) return { id: userExists.id, email: userExists.email, isRegistered: userExists.isRegistered }; } }) ], // 切换为JWT会话策略 session: { strategy: "jwt" }, // 可选:配置JWT过期时间(默认30天) jwt: { maxAge: 30 * 24 * 60 * 60, } }; export default NextAuth(authOptions);
2. 验证效果
修改后,通过CredentialsProvider登录时:
authorize验证通过后返回用户对象jwt回调会将用户信息存入JWT令牌- 当前端调用
useSession()或getSession()时,session回调会触发,从JWT中提取信息并返回完整会话 - 会话信息通过JWT维护,无需依赖数据库存储(数据库仍会存储用户基础信息)
备选方案(不推荐)
如果必须坚持使用database会话策略,需要在authorize中返回完整的数据库用户对象(而非自定义对象),同时手动触发会话创建逻辑,但这种方式需要深入NextAuth内部机制,复杂度较高,不建议采用。
内容的提问来源于stack exchange,提问作者Matthew Mannning
相关产品推荐
相关产品推荐

