NextAuth对接Coinbase时MongoDB存储的AccessToken重登录未刷新问题
NextAuth Coinbase 身份认证Token不更新问题修复方案
问题根因
你的代码存在4个核心问题导致重新登录时token不更新:
- 未显式指定session策略,配置了数据库的情况下NextAuth v3默认使用
databasesession策略,你编写的jwt回调不会按预期触发 - jwt回调中token取值来源错误,OAuth提供商返回的access_token、refresh_token、过期时间等凭证存储在
account对象中,而非user.data,重新登录时user是从数据库读取的旧数据,自然拿到的都是旧token - 仅更新了JWT中的token,没有同步更新MongoDB中存储的account凭证信息,后续读取数据库时还是旧数据
- 刷新token逻辑执行后也没有同步更新数据库中的凭证,导致新旧数据不一致
修复步骤
- 显式指定session策略为
jwt,确保jwt回调正常触发 - 修正jwt回调逻辑,只要存在
account对象(不管是首次登录还是重新登录),就用account中的新凭证覆盖旧数据,同时同步更新MongoDB中的account表 - 修正token取值来源,统一从
account对象取OAuth返回的凭证 - 补充refreshAccessToken执行成功后的数据库同步逻辑
- 完善session回调,将必要字段返回给前端
完整修改后代码
import NextAuth from 'next-auth'; import Providers from 'next-auth/providers'; // 如果你用mongoose操作MongoDB,提前引入你的Account模型,或者直接用你自己的数据库操作方法 import Account from '../../../models/Account'; // 替换为你自己的Account模型路径 async function refreshAccessToken(token) { try { const url = "https://api.coinbase.com/oauth/token?" + new URLSearchParams({ client_id: process.env.COINBASE_CLIENT_ID, client_secret: process.env.COINBASE_SECRET_ID, grant_type: "refresh_token", refresh_token: token.refreshToken, }) const response = await fetch(url, { headers: { "Content-Type": "application/x-www-form-urlencoded", }, method: "POST", }) const refreshedTokens = await response.json() if (!response.ok) { throw refreshedTokens } const newTokenData = { ...token, accessToken: refreshedTokens.access_token, accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000, refreshToken: refreshedTokens.refresh_token ?? token.refreshToken, } // 刷新token后同步更新数据库 await Account.updateOne( { userId: token.user.id, provider: 'coinbase' }, { access_token: newTokenData.accessToken, refresh_token: newTokenData.refreshToken, expires_at: Math.floor(newTokenData.accessTokenExpires / 1000) } ) return newTokenData } catch (error) { console.log(error) return { ...token, error: "RefreshAccessTokenError", } } } export default NextAuth({ // 显式指定session策略为jwt session: { strategy: 'jwt', maxAge: 30 * 24 * 60 * 60 // 30天过期,可自行调整 }, providers: [ Providers.Coinbase({ clientId: process.env.COINBASE_CLIENT_ID, clientSecret: process.env.COINBASE_SECRET_ID, callbackUrl: process.env.COINBASE_CALLBACKURL, scope: "wallet:accounts:read", }), ], callbacks: { async jwt({ token, user, account, profile, isNewUser }) { // 只要有account对象,说明是 OAuth 登录流程(包含首次登录和重新登录),用最新的凭证更新 if (account && user) { const newToken = { accessToken: account.access_token, accessTokenExpires: account.expires_at * 1000, refreshToken: account.refresh_token, user, } // 同步更新数据库中的account凭证 await Account.updateOne( { userId: user.id, provider: 'coinbase' }, { access_token: newToken.accessToken, refresh_token: newToken.refreshToken, expires_at: account.expires_at }, { upsert: true } // 不存在就新建,避免首次登录报错 ) return newToken } // 未过期直接返回旧token if (Date.now() < token.accessTokenExpires) { return token } // 过期则刷新 return refreshAccessToken(token) }, async session(session, token) { session.accessToken = token.accessToken session.refreshToken = token.refreshToken session.accessTokenExpires = token.accessTokenExpires session.user = token.user session.error = token.error return session } }, events: { async signIn(message) { console.log('success signin') }, async signOut(message) { console.log('success signout') }, async createUser(message) { console.log('success user create') }, async updateUser(message) { console.log('success update user') }, async session(message) { console.log('success session') }, async error(message) { console.log('error') } }, database: `mongodb+srv://${process.env.NOSQL_USER}:${process.env.NOSQL_PWD}@${process.env.NOSQL_HOST}/${process.env.NOSQL_TABLE}`, });
注意事项
如果你没有自定义Account模型,可以替换为你自己使用的MongoDB操作逻辑,只要能更新对应用户的coinbase凭证即可;如果使用的是NextAuth v4版本,导入路径和部分API会有差异,需要对应调整。
内容的提问来源于stack exchange,提问作者Mikhael Bailly
相关产品推荐
相关产品推荐

