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

@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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 01:40:39