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

Tauri应用Firebase云函数刷新Google Access Token遇invalid_grant错误

Google OAuth2.0令牌刷新问题排查与解决

问题描述

我正在Tauri桌面应用中结合Firebase云函数,试验Google OAuth2.0授权与Access Token刷新功能。目前通过Firebase的signInWithPopup()获取Access Token、Refresh Token和Id Token:

const provider = new GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/calendar')
        
const result = await signInWithPopup(auth, provider);

const credentials = GoogleAuthProvider.credentialFromResult(result)

const uid = result.user.uid
const idToken = await result.user.getIdToken()
const accessToken = credentials?.accessToken
const refreshToken = result.user.refreshToken

随后调用Firebase云函数,传入Id Token和Refresh Token以刷新过期的Access Token:

const response = await fetch('https://us-central1-axel-86.cloudfunctions.net/refreshAccessToken', {
  method: 'POST',
  headers: {'Content-Type':'application/json'},
  body: JSON.stringify({ data: { uid, idToken, refreshToken }}),
});

云函数代码如下:

export const refreshAccessToken = onRequest({ cors: "*",  }, async (req, res) => {
  try {
    const { idToken, refreshToken } = req.body.data;
 
    const refreshedAccessToken = await refreshGoogleAccessToken(refreshToken);
    
    res.json({ refreshedAccessToken });
  } catch (e) {
    throw new HttpsError("unknown", 'Error', e)
  }
})

async function refreshGoogleAccessToken(refreshToken: string) {
  const oauth2Client = new google.auth.OAuth2(
      googleOauth.clientId,
      googleOauth.clientSecret,
      ''
  );
  oauth2Client.setCredentials({ refresh_token: refreshToken, scope: `https://www.googleapis.com/auth/calendar` });
  
  logger.debug("credentials - ok")

  // 此处执行失败
  const refreshed = await oauth2Client.refreshAccessToken();

  logger.debug("refreshed - ok", refreshed )
  
  return refreshed.credentials.access_token;
}

遇到的问题:oauth2Client.refreshAccessToken()执行失败,云函数日志仅显示"credentials - ok",并抛出invalid_grant错误。

疑问:问题出在哪里?如何正确刷新Access Token?是否需要使用googleapis库?

注:我了解正确流程应使用signInWithRedirect()并将Refresh Token存储到数据库,但当前为试验场景,此方式不应阻碍Token刷新。

解决方案

1. invalid_grant错误的核心原因

Firebase通过signInWithPopup()返回的result.user.refreshToken是Firebase自身的刷新令牌,并非Google OAuth2原生的刷新令牌。直接将其传给Google OAuth2客户端进行刷新,Google自然无法识别,从而触发invalid_grant错误。

2. 正确获取Google原生Refresh Token

修改前端授权代码,强制请求Google的离线访问权限,才能拿到Google认可的原生刷新令牌:

const provider = new GoogleAuthProvider();
provider.addScope('https://www.googleapis.com/auth/calendar');
// 强制获取离线访问权限,确保返回Google原生refresh_token
provider.setCustomParameters({
  access_type: 'offline',
  prompt: 'consent' // 试验场景下强制每次授权都返回新的refresh_token
});
        
const result = await signInWithPopup(auth, provider);

// 从credentials中提取Google原生refresh_token,而非result.user.refreshToken
const credentials = GoogleAuthProvider.credentialFromResult(result);
const googleRefreshToken = credentials?.refreshToken;
const uid = result.user.uid;
const idToken = await result.user.getIdToken();
const accessToken = credentials?.accessToken;

3. 云函数代码修正

云函数中使用googleapis库是正确的,只需确保传入的是Google原生刷新令牌,同时调整错误返回逻辑更直观:

export const refreshAccessToken = onRequest({ cors: "*",  }, async (req, res) => {
  try {
    const { idToken, googleRefreshToken } = req.body.data;
 
    const refreshedAccessToken = await refreshGoogleAccessToken(googleRefreshToken);
    
    res.json({ refreshedAccessToken });
  } catch (e) {
    res.status(500).json({ error: '令牌刷新失败', details: e.message });
  }
})

async function refreshGoogleAccessToken(refreshToken: string) {
  const oauth2Client = new google.auth.OAuth2(
      googleOauth.clientId,
      googleOauth.clientSecret,
      '' // 需与Google Cloud控制台配置的redirect_uri一致,试验场景留空可正常使用
  );
  oauth2Client.setCredentials({ refresh_token: refreshToken });
  // 无需重复设置scope,refreshToken已绑定授权时的权限范围
  
  logger.debug("credentials - ok")

  const refreshed = await oauth2Client.refreshAccessToken();

  logger.debug("refreshed - ok", refreshed )
  
  return refreshed.credentials.access_token;
}

4. 关键注意事项

  • Google Cloud控制台中,OAuth2客户端类型必须设置为桌面应用,否则可能无法正常获取Google原生refresh_token。
  • 试验场景下,prompt: 'consent'可强制每次授权返回新的refresh_token,避免旧令牌被回收导致的刷新失败。
  • googleapis库是必须的,它封装了Google OAuth2的令牌刷新逻辑,无需手动调用底层API。

内容的提问来源于stack exchange,提问作者Axel Productions 86

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 01:23:16