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

如何通过Firebase beforeSignIn函数向客户端返回数据并获取认证属性

解决Firebase Auth获取Google Classroom API所需凭据的问题

先明确几个关键凭据的性质与获取限制

首先要理清:你要的5个凭据里,clientId和clientSecret是应用级凭据,不是用户认证流程返回的用户级数据,和其他三个用户令牌属性完全不同:

  • clientId:可直接从Firebase控制台(Authentication → 登录方法 → Google)或Google Cloud控制台(API和服务 → 凭据 → OAuth 2.0客户端ID)获取,可安全在前端代码中使用(GoogleAuthProvider初始化默认已配置,也可显式传入)。
  • clientSecret:绝对不能暴露在前端!这是后端专用的凭据,用于刷新令牌或验证身份,需存储在后端环境变量中,仅在服务端调用Google API时使用。

获取用户级令牌(accessToken、refreshToken、expiryTime)

1. accessToken:现有代码已实现

你通过signInWithPopup返回的credential.accessToken就是Google OAuth的访问令牌,可直接用于调用Classroom API,有效期约1小时。

2. refreshToken与expiryTime:通过云函数传递到客户端

前端无法直接获取Google的离线刷新令牌(出于安全限制),但可通过beforeSignIn触发器将其存储后,再由客户端读取:

方案一:存储到用户自定义Claims

云函数代码:

const admin = require('firebase-admin');
admin.initializeApp();

exports.beforeSignIn = functions.auth.user().beforeSignIn(async (user, context) => {
  const refreshToken = context.credential.refreshToken;
  const expiryTime = context.credential.expirationTime;

  // 仅当存在refreshToken时更新Claims(避免重复操作)
  if (refreshToken) {
    await admin.auth().setCustomUserClaims(user.uid, {
      google_refresh_token: refreshToken,
      google_token_expiry: expiryTime
    });
  }
});

客户端代码(登录后读取):

signInWithPopup(auth, provider)
  .then(async (result) => {
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const accessToken = credential.accessToken;
    
    // 获取包含自定义Claims的ID Token
    const idTokenResult = await result.user.getIdTokenResult();
    const refreshToken = idTokenResult.claims.google_refresh_token;
    const expiryTime = idTokenResult.claims.google_token_expiry;
    
    // 此时已拿到三个用户令牌属性
  })

注意:自定义Claims有1000字节大小限制,若需存储更多关联数据,建议用方案二。

方案二:存储到Firestore/实时数据库

云函数代码:

exports.beforeSignIn = functions.auth.user().beforeSignIn(async (user, context) => {
  const refreshToken = context.credential.refreshToken;
  const expiryTime = context.credential.expirationTime;

  if (refreshToken) {
    await admin.firestore().collection('user_google_tokens').doc(user.uid).set({
      refresh_token: refreshToken,
      expiry_time: expiryTime,
      updated_at: admin.firestore.FieldValue.serverTimestamp()
    }, { merge: true });
  }
});

客户端代码(登录后读取):

signInWithPopup(auth, provider)
  .then(async (result) => {
    const credential = GoogleAuthProvider.credentialFromResult(result);
    const accessToken = credential.accessToken;
    const user = result.user;
    
    // 从Firestore读取令牌数据
    const tokenDoc = await firestore.collection('user_google_tokens').doc(user.uid).get();
    if (tokenDoc.exists) {
      const { refresh_token: refreshToken, expiry_time: expiryTime } = tokenDoc.data();
      // 使用凭据调用API
    }
  })

3. expiryTime的替代获取方式

除了云函数传递,也可直接解码accessToken得到过期时间(accessToken是JWT格式):

import jwtDecode from 'jwt-decode';

// 在获取accessToken后
const decodedToken = jwtDecode(accessToken);
const expiryTime = decodedToken.exp * 1000; // 转换为毫秒时间戳

最终凭据整合与使用建议

  • 前端持有:accessToken、clientId、从后端获取的refreshToken/expiryTime
  • 后端持有:clientSecret、refreshToken(可选,用于集中刷新令牌)
  • 调用Classroom API时,若accessToken过期,建议由后端使用clientSecret+refreshToken刷新令牌,再返回新的accessToken给前端,避免clientSecret暴露风险。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 14:54:18