@sidebase/nuxt-auth结合Laravel Passport刷新令牌失效问题排查
问题:Nuxt 3 + @sidebase/nuxt-auth + Laravel Passport PKCE认证令牌自动刷新失效
我在Nuxt 3项目中使用@sidebase/nuxt-auth,搭配基于Laravel Passport的Laravel API实现PKCE OAuth2认证流程。目前已完成Laravel端与Nuxt端的认证流程开发,可正常获取并存储令牌;同时使用ofetch实现了自定义拦截器,确保每次请求都会携带从会话中获取的access_token。但出现access_token过期后无法自动刷新的问题,我尝试了@sidebase/nuxt-auth中的JWT回调但未生效。想咨询是缺少相关配置,还是需要自行编写刷新令牌的自定义逻辑?
当前认证配置代码
import {NuxtAuthHandler} from '#auth' import useCustomFetch from "~/composables/useCustomFetch"; export default NuxtAuthHandler({ providers: [ { id: 'passport', name: 'Passport', type: 'oauth', version: '2.0', authorization: { url: "https://example.com/oauth/authorize", params: { scope: '', prompt: 'front', }, }, clientSecret: 'awd', clientId: "96695f40-1578-4b7c-974b-181e0344dcac", token: 'https://example.com/api/v1/oauth/token', userinfo: 'https://example.com/api/v1/user', checks: ['pkce'], profile(profile: { success: { user: any } }) { const data = profile.success.user return { id: data.id, name: data.first_name + " " + data.last_name, email: data.email, }; }, } ], cookies: { }, callbacks: { async jwt({token, account, user}) { if (account && user) { return { access_token: account.access_token, refresh_token: account.refresh_token, accessTokenExpires: account.expires_at, user } } // @ts-ignore if (Date.now() < token.accessTokenExpires * 1000) { return token } return await refreshAccessToken(token); }, async session({session, token}) { // @ts-ignore session.user = token.user // @ts-ignore session.access_token = token.access_token // @ts-ignore session.error = token.error return session }, }, events: { async signOut() { try { await useCustomFetch('/oauth/tokens/revoke', { method: 'POST' }) } catch (e) { console.log(e); } }, } }) async function refreshAccessToken(token: any) { try { const url = "https://example.com/api/v1/oauth/token"; // @ts-ignore const refreshedToken: AuthResponse = await $fetch(url, { headers: { "Content-Type": "application/json", }, method: "POST", body: { grant_type: 'refresh_token', refresh_token: token.refresh_token, client_id: "96695f40-1578-4b7c-974b-181e0344dcac" } }); token.access_token = refreshedToken.access_token; token.accessTokenExpires = Date.now() + refreshedToken.expires_at * 1000; token.refresh_token = refreshedToken.refresh_token; return { ...token } } catch (error) { console.log(error) return { ...token, error: "RefreshAccessTokenError", } } } interface AuthResponse { access_token: string, refresh_token: string, token_type: string, expires_at: number, }
问题分析与修复步骤
1. 令牌过期时间计算错误
在refreshAccessToken函数中,accessTokenExpires的计算逻辑存在错误:
- Laravel Passport返回的
expires_at是令牌到期的时间戳(秒),并非有效期时长。 - 当前代码用
Date.now() + refreshedToken.expires_at * 1000会将过期时间错误设置为「当前时间+到期时间戳」,导致令牌被立刻判定为过期。
修复代码:
// 替换refreshAccessToken中的过期时间计算行 token.accessTokenExpires = refreshedToken.expires_at * 1000;
2. 确保JWT回调被触发
@sidebase/nuxt-auth的jwt回调默认在页面初始化、路由切换或调用useAuth()相关方法时触发。如果自定义fetch拦截器未与auth系统联动,令牌过期返回401时不会自动触发刷新逻辑。需在拦截器中添加401处理:
修改useCustomFetch组合式函数:
// ~/composables/useCustomFetch.ts export default async function useCustomFetch(url: string, options: any = {}) { const auth = useAuth(); // 请求拦截:携带access_token options.headers = { ...options.headers, Authorization: `Bearer ${auth.value?.access_token}` }; try { return await $fetch(url, options); } catch (error: any) { // 捕获401错误,尝试刷新令牌 if (error.response?.status === 401 && !options._retry) { options._retry = true; // 避免无限重试 try { // 手动触发会话更新,调用jwt回调刷新令牌 await auth.refresh(); // 更新请求头的access_token options.headers.Authorization = `Bearer ${auth.value?.access_token}`; // 重新发起请求 return await $fetch(url, options); } catch (refreshError) { // 刷新失败,登出用户 await auth.signOut(); throw refreshError; } } throw error; } }
3. 验证Passport刷新请求参数
- 若你的Passport客户端是PKCE公开客户端,刷新请求无需传
client_secret,当前代码已正确只传client_id和refresh_token,无需修改。 - 若为保密客户端,需在请求体中添加
client_secret参数。
4. 完善Nuxt Auth全局配置
确保nuxt.config.ts中auth模块配置正确,开启全局中间件和定期会话检查:
// nuxt.config.ts export default defineNuxtConfig({ modules: ['@sidebase/nuxt-auth'], auth: { globalAppMiddleware: true, // 路由切换时自动检查令牌状态 enableSessionRefreshPeriodically: 30000, // 每30秒自动刷新会话(可选) } });
5. 调试JWT回调
可在jwt回调中添加日志,确认是否被触发:
async jwt({token, account, user}) { console.log('JWT回调触发,当前令牌:', token); // 添加调试日志 // ... 原有逻辑 }
总结
核心问题是令牌过期时间计算错误导致JWT回调无法正确判断令牌状态,配合拦截器的401处理逻辑,即可实现自动刷新功能。无需完全自定义刷新逻辑,@sidebase/nuxt-auth的JWT回调机制可正常工作,只需修正配置和计算逻辑即可。
内容的提问来源于stack exchange,提问作者Hassan Hammoud
相关产品推荐
相关产品推荐

