Tauri应用Firebase云函数刷新Google Access Token遇invalid_grant错误
问题描述
我正在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

