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

无法获取refresh_token,如何保持Gmail克隆应用用户持续登录?

解决Gmail克隆应用中Google登录无法获取refresh_token及自动刷新问题

一、为什么没拿到refresh_token?

Google OAuth的规则里,即使设置了accessType:"offline",以下情况也不会返回refresh_token:

  • 用户之前已经授权过相同的权限范围,且没有强制重新授权
  • 应用处于发布状态但未通过审核,且用户不是添加的测试用户
  • 请求的scope和之前授权的不一致

二、修改配置获取refresh_token

调整useGoogleLogin的配置,同时注意授权码换token的流程(因为你设置了responseType:"code",直接返回的是授权码,不是最终的token):

const login = useGoogleLogin({
    clientId: CLIENT_ID,
    onSuccess: async (response) => {
        // 用授权码code换取access_token和refresh_token
        const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
            method: 'POST',
            headers: {'Content-Type': 'application/x-www-form-urlencoded'},
            body: new URLSearchParams({
                code: response.code,
                client_id: CLIENT_ID,
                client_secret: CLIENT_SECRET, // 注意:生产环境请移到后端处理,不要暴露在前端
                redirect_uri: 'postmessage',
                grant_type: 'authorization_code'
            })
        }).then(res => res.json());

        // 存储关键信息到本地
        localStorage.setItem('refresh_token', tokenRes.refresh_token);
        localStorage.setItem('access_token', tokenRes.access_token);
        localStorage.setItem('expires_at', Date.now() + tokenRes.expires_in * 1000);
        
        // 启动自动刷新
        setupAutoRefresh();
    },
    accessType: "offline",
    responseType: "code",
    prompt: "select_account consent", // 强制用户选账号+重新授权,确保返回refresh_token
    scope: 'https://www.googleapis.com/auth/gmail.readonly https://www.googleapis.com/auth/gmail.modify', // 统一scope和实际授权保持一致
});

三、实现自动刷新token逻辑

1. 刷新token的函数

async function refreshAccessToken() {
    const refreshToken = localStorage.getItem('refresh_token');
    if (!refreshToken) return null;

    try {
        const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
            method: 'POST',
            headers: {'Content-Type': 'application/x-www-form-urlencoded'},
            body: new URLSearchParams({
                refresh_token: refreshToken,
                client_id: CLIENT_ID,
                client_secret: CLIENT_SECRET, // 生产环境移到后端
                grant_type: 'refresh_token'
            })
        }).then(res => res.json());

        // 更新本地存储的token和过期时间
        localStorage.setItem('access_token', tokenRes.access_token);
        localStorage.setItem('expires_at', Date.now() + tokenRes.expires_in * 1000);
        
        return tokenRes.access_token;
    } catch (err) {
        // 刷新失败,清空存储并引导重新登录
        localStorage.removeItem('refresh_token');
        localStorage.removeItem('access_token');
        localStorage.removeItem('expires_at');
        alert('登录状态过期,请重新登录');
        return null;
    }
}

2. 自动刷新定时器

function setupAutoRefresh() {
    const expiresAt = parseInt(localStorage.getItem('expires_at'));
    if (!expiresAt) return;

    // 提前5分钟触发刷新
    const refreshDelay = expiresAt - Date.now() - 5 * 60 * 1000;
    if (refreshDelay > 0) {
        setTimeout(async () => {
            const newToken = await refreshAccessToken();
            if (newToken) {
                // 刷新成功后,继续设置下一次刷新
                setupAutoRefresh();
            }
        }, refreshDelay);
    }
}

四、关键注意事项

  • client_secret安全:绝对不要在前端代码中直接写client_secret,生产环境必须把token换取和刷新逻辑放在后端,前端调用后端接口即可,避免密钥泄露
  • 存储安全:localStorage容易被XSS攻击,建议用HTTP-only cookie存储refresh_token,由后端管理
  • 异常处理:添加网络错误、token无效等异常情况的处理,避免应用崩溃

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:47:06