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

NextJS + Supabase Auth:如何正确刷新Discord的provider_token/access token

NextJS 13 + Supabase Auth 处理Discord Provider Token失效的最佳实践

针对你遇到的Discord provider_token 几分钟后变为null的问题,以下是实际项目中验证有效的解决方案:

1. 主动持久化第三方提供商Token到数据库

Supabase Auth的会话对象默认不会长期存储第三方平台的access_token和refresh_token,仅在登录初期临时持有。因此需要在登录成功后,将这些字段存入用户表或专用的Token存储表:

import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'

const supabase = createClientComponentClient()

// 监听登录状态,保存第三方Token
supabase.auth.onAuthStateChange(async (event, session) => {
  if (event === 'SIGNED_IN' && session?.provider_token) {
    await supabase
      .from('users')
      .update({
        provider_token: session.provider_token,
        provider_refresh_token: session.provider_refresh_token,
        provider_token_expires_at: new Date(session.expires_at * 1000)
      })
      .eq('id', session.user.id)
  }
})

2. 实现第三方Token自动刷新逻辑

Discord的access_token有效期为15分钟,需通过refresh_token主动刷新。可以借助NextJS的API路由实现服务端刷新:

// app/api/refresh-discord-token/route.js
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'

export async function POST(request) {
  const supabase = createServerComponentClient({ cookies })
  const { user } = await supabase.auth.getUser()

  if (!user) return new Response('Unauthorized', { status: 401 })

  // 从数据库获取refresh_token
  const { data: userData } = await supabase
    .from('users')
    .select('provider_refresh_token')
    .eq('id', user.id)
    .single()

  if (!userData?.provider_refresh_token) return new Response('无可用刷新Token', { status: 400 })

  // 调用Discord刷新接口
  const response = await fetch('https://discord.com/api/oauth2/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      client_id: process.env.DISCORD_CLIENT_ID,
      client_secret: process.env.DISCORD_CLIENT_SECRET,
      grant_type: 'refresh_token',
      refresh_token: userData.provider_refresh_token
    })
  })

  const tokenData = await response.json()
  if (!response.ok) return new Response(tokenData.error, { status: response.status })

  // 更新数据库中的Token信息
  await supabase
    .from('users')
    .update({
      provider_token: tokenData.access_token,
      provider_refresh_token: tokenData.refresh_token || userData.provider_refresh_token,
      provider_token_expires_at: new Date(Date.now() + tokenData.expires_in * 1000)
    })
    .eq('id', user.id)

  return new Response(JSON.stringify({ access_token: tokenData.access_token }), { status: 200 })
}

3. 按需获取并使用存储的Token

不再依赖Supabase会话中的provider_token,而是从数据库获取最新Token,过期前主动触发刷新:

// 获取可用的Discord Token
async function getDiscordToken() {
  const supabase = createClientComponentClient()
  const { user } = await supabase.auth.getUser()
  if (!user) return null

  const { data: userData } = await supabase
    .from('users')
    .select('provider_token, provider_token_expires_at')
    .eq('id', user.id)
    .single()

  if (!userData) return null

  // 提前5分钟触发刷新
  const expiresAt = new Date(userData.provider_token_expires_at)
  const needRefresh = expiresAt.getTime() - Date.now() < 5 * 60 * 1000

  if (needRefresh) {
    const response = await fetch('/api/refresh-discord-token', { method: 'POST' })
    if (response.ok) {
      const { access_token } = await response.json()
      return access_token
    }
    return null
  }

  return userData.provider_token
}

4. 确认Supabase Auth持久化配置(可选)

确保Supabase客户端配置了正确的会话持久化策略,避免自身会话意外失效:

const supabase = createClientComponentClient({
  options: {
    auth: {
      storage: typeof window !== 'undefined' ? window.localStorage : null,
      autoRefreshToken: true,
      persistSession: true
    }
  }
})

核心思路:Supabase Auth的会话核心是管理自身的用户认证状态,第三方平台的Token属于临时凭证,需要自行接管存储与刷新逻辑,避免依赖Supabase会话的临时存储。

内容的提问来源于stack exchange,提问作者Excision

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 22:10:42