NextAuth.js合并Credentials与Google Provider会话数据问题(NextJS)
问题描述
我正在开发一个使用NextAuth v4.19的项目,采用Credentials(数据库)和Google作为登录提供商。使用Credentials提供商时一切正常,能获取包含所有数据库字段的session.user;但通过Google注册/登录时,返回的会话仅包含谷歌返回的数据及令牌信息。我需要将谷歌返回的数据与我的用户数据库字段合并,并传递到客户端的session对象中。
相关代码如下:
export const authOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), CredentialsProvider({ name: "Credentials", async authorize(credentials, req) { const { username, password } = credentials; const user = await User.findOne({ email: username }).exec(); const isValid = await user.comparePassword(password); if (!isValid) { return false; } return user; }, }), ], callbacks: { signIn: async ({ user, account, profile }) => { console.log("signIn", user, account, profile); if (account.provider === "google") { const existingUser = await User.findOne({ email: user.email, }).exec(); if (existingUser) { console.log( "Google user already exists, returning existing user", existingUser ); return existingUser; } console.log("New google account, saving in db..."); const newUser = await User.create({ name: user.name, email: user.email, password: "googlepassword", // @TOOD: bcrypt this photo: user.image, provider: true, providerName: "google", }); console.log("Google user saved, returning:", newUser); return newUser; } else { return true; } }, // Persist the OAuth access_token and or the user id to the token right after signin jwt: async ({ token, user }) => { user && (token.user = user); return token; }, session: async ({ session, token, user }) => { session.user = token.user; return session; }, }, secret: process.env.NEXTAUTH_SECRET, pages: { signIn: "/auth/signin", signOut: "/auth/signout", error: "/auth/signin", // Error code passed in query string as ?error= verifyRequest: "/auth/verify-request", // (used for check email message) newUser: "/auth/new-user", // New users will be directed here on first sign in (leave the property out if not of interest) }, }; export default NextAuth(authOptions);
使用Credentials登录时的session.user:
{ "_id": "640f9ecde855917558d80449", "email": "email@gmail.com", "password": "$2b$10$Skj418vLiKyc0yM/Wg3mJu9c5ahjn175zALf/XxgCyJzDz9vxCJD6", "name": "Name Surname", "provider": true, "providerName": "google", "photo": "https://lh3.googleusercontent.com/a", "friendshipRequests": [], "friends": [], "favorites": [], "createdAt": "2023-03-13T22:08:13.596Z", "updatedAt": "2023-03-13T22:08:13.596Z", "__v": 0 }
使用Google登录时的session.user:
{ "id": "114524123847825110849", "name": "Name Surname", "email": "email@gmail.com", "image": "https://lh3.googleusercontent.com/a/AGNm..." }
解决方案
问题出在signIn回调的返回值上——NextAuth的signIn回调对于OAuth提供商,不能直接返回数据库用户对象,它仅接受布尔值或URL字符串来控制登录流程。你之前在Google登录分支返回existingUser或newUser,会被NextAuth当作true处理,导致后续jwt回调拿到的user依然是Google返回的原始数据,而非数据库中的完整用户对象。
修改步骤如下:
- 调整
signIn回调,完成数据库操作后返回true,不要返回用户对象 - 在
jwt回调中区分登录提供商,Google登录时主动查询数据库获取完整用户信息
修改后的完整代码:
export const authOptions = { providers: [ GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET, }), CredentialsProvider({ name: "Credentials", async authorize(credentials, req) { const { username, password } = credentials; const user = await User.findOne({ email: username }).exec(); if (!user) { return null; // 替换返回false,符合NextAuth规范 } const isValid = await user.comparePassword(password); if (!isValid) { return null; } return user; }, }), ], callbacks: { signIn: async ({ user, account }) => { if (account.provider === "google") { const existingUser = await User.findOne({ email: user.email }).exec(); // 不存在则创建新用户 if (!existingUser) { await User.create({ name: user.name, email: user.email, password: "googlepassword", // 记得后续用bcrypt加密 photo: user.image, provider: true, providerName: "google", }); } // 始终返回true允许登录 return true; } return true; }, jwt: async ({ token, user, account }) => { // Credentials登录时直接使用数据库返回的完整用户 if (account?.provider === "credentials") { token.user = user; } // Google登录时查询数据库获取完整用户 if (account?.provider === "google") { const dbUser = await User.findOne({ email: token.email }).exec(); token.user = dbUser; } return token; }, session: async ({ session, token }) => { session.user = token.user; return session; }, }, secret: process.env.NEXTAUTH_SECRET, pages: { signIn: "/auth/signin", signOut: "/auth/signout", error: "/auth/signin", verifyRequest: "/auth/verify-request", newUser: "/auth/new-user", }, }; export default NextAuth(authOptions);
关键修改说明
signIn回调:仅负责用户的创建/校验逻辑,返回true允许登录流程继续,不再返回用户对象jwt回调:区分登录提供商,Google登录时主动从数据库拉取完整用户数据,确保token.user是数据库中的完整对象- Credentials授权优化:将返回
false改为null,贴合NextAuth的官方规范
修改完成后,Google登录的session.user会和Credentials登录保持一致,包含数据库中的所有字段。
内容的提问来源于stack exchange,提问作者rsilva
相关产品推荐
相关产品推荐

