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

无需重载页面的Firebase身份验证JWT自动刷新问题咨询

JWT刷新后请求仍报过期问题的解决方案

核心问题原因

  • 刷新获取新token后未同步更新cookie存储:你的代码仅在初始化onAuthStateChanged触发、拿到初始token时执行了cookie写入逻辑,定时器触发刷新、拿到新token后,仅更新了React状态token,没有同步更新cookie中的token值。如果你的接口请求是从cookie读取token携带,请求自然会使用旧的已过期token,手动刷新页面后会重新触发onAuthStateChanged逻辑写入新token,所以问题会临时修复。
  • 存在闭包陷阱:setTimeout回调中引用的timeToExpiry可能存在陈旧闭包值问题,极端场景会导致刷新时机偏差。
  • 未兼容currentUser为空场景:异步执行getIdToken时,firebase.auth().currentUser可能已被置空,直接调用方法会抛出异常。

修复后代码

React.useEffect(() => {
    // 存储定时器ID,用于卸载时清理
    let refreshTimer = null;
    const unsubscribe = firebase.auth().onAuthStateChanged(async (auth) => {
        if (auth) {
            const token = await auth.getIdToken();
            setToken(token)
            nookies.destroy({}, "token");
            nookies.set({}, "token", token, {
                path: "/"
            });

            setAuth(auth);

            const val = jwt_decode(token) as any;
            const exp = Number(val.exp);

            let timeToExpiry = moment(exp * 1000 - 1000 * 5).diff(moment());
            const updateToken = () => {
                refreshTimer = setTimeout(async () => {
                    console.log("REFRESHING TOKEN");
                    const currentUser = firebase.auth().currentUser;
                    // 兼容用户已登出场景,不再执行刷新
                    if (!currentUser) return;
                    try {
                        const newToken = await currentUser.getIdToken(true);
                        setToken(newToken);
                        // 新增:同步更新cookie中的token
                        nookies.destroy({}, "token");
                        nookies.set({}, "token", newToken, {
                            path: "/"
                        });
                        const val = jwt_decode(newToken) as any;
                        const exp = Number(val.exp);
                        timeToExpiry = moment(exp * 1000 - 1000 * 5).diff(moment());
                        updateToken();
                    } catch (e) {
                        // 刷新失败走登出逻辑
                        await firebase.auth().signOut();
                    }
                }, timeToExpiry)
            }
            updateToken();
        }
        else {
            setToken("");
            nookies.destroy({}, "token");
            setAuth(null);
            // 登出时清理定时器
            if (refreshTimer) clearTimeout(refreshTimer);

            var provider = new firebase.auth.GoogleAuthProvider();
            provider.setCustomParameters({
                prompt: 'select_account'
            });

            firebase.auth().signInWithRedirect(provider);
        }
    })
    // 组件卸载时清理监听和定时器
    return () => {
        unsubscribe();
        if (refreshTimer) clearTimeout(refreshTimer);
    }
}, [])

额外优化建议

  • 接口请求时优先从React状态读取最新token,或者每次请求前直接调用firebase.auth().currentUser?.getIdToken()实时获取有效token,避免依赖静态存储的过期值
  • 可以给接口请求添加响应拦截器,收到401过期错误时主动触发一次token刷新,刷新成功后重发原请求,进一步降低过期概率

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.19 14:36:03