You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextAuth对接Coinbase时MongoDB存储的AccessToken重登录未刷新问题

NextAuth Coinbase 身份认证Token不更新问题修复方案

问题根因

你的代码存在4个核心问题导致重新登录时token不更新:

  • 未显式指定session策略,配置了数据库的情况下NextAuth v3默认使用database session策略,你编写的jwt回调不会按预期触发
  • jwt回调中token取值来源错误,OAuth提供商返回的access_token、refresh_token、过期时间等凭证存储在account对象中,而非user.data,重新登录时user是从数据库读取的旧数据,自然拿到的都是旧token
  • 仅更新了JWT中的token,没有同步更新MongoDB中存储的account凭证信息,后续读取数据库时还是旧数据
  • 刷新token逻辑执行后也没有同步更新数据库中的凭证,导致新旧数据不一致

修复步骤

  1. 显式指定session策略为jwt,确保jwt回调正常触发
  2. 修正jwt回调逻辑,只要存在account对象(不管是首次登录还是重新登录),就用account中的新凭证覆盖旧数据,同时同步更新MongoDB中的account表
  3. 修正token取值来源,统一从account对象取OAuth返回的凭证
  4. 补充refreshAccessToken执行成功后的数据库同步逻辑
  5. 完善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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 03:09:05