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
相关产品推荐
相关产品推荐

