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

使用Firebase数据库触发器添加Google Calendar事件的OAuth2问题排查

解决Firebase云函数调用Google Calendar API的OAuth2 Token问题

看起来你踩了Google OAuth2和Calendar API集成的常见坑——用错了Token类型,还没处理Token过期的问题。我来一步步帮你理清:

错误根源

你日志里的No access, refresh token or API key is set或者No refresh token is set,本质是两个问题:

  • 你传给云函数的Token不是Google Calendar API认可的OAuth2访问令牌(access token)
  • 就算拿到了access token,它有效期只有1小时,你没有存储刷新令牌(refresh token)来自动续期

你需要哪种Token?

你需要的是带有Calendar权限的Google OAuth2 access token + refresh token:

  • access token是直接调用Calendar API的临时凭证,有效期1小时
  • refresh token是长期凭证,用来在access token过期时自动获取新的access token(只要用户没撤销权限就一直有效)

Android端正确获取授权凭证

你之前用的serverAuthCode/idToken/Firebase ID Token都不对,正确步骤如下:

1. 配置GoogleSignInOptions

必须请求Calendar权限、离线访问(才能拿到refresh token),并且要用Web应用的Client ID(不是Android的Client ID):

val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
    // 请求Calendar读写权限
    .requestScopes(Scope("https://www.googleapis.com/auth/calendar"))
    // 填你在Google Cloud Console里创建的Web应用Client ID
    .requestServerAuthCode("YOUR_WEB_CLIENT_ID")
    // 关键:请求离线访问,才能拿到refresh token
    .requestOfflineAccess()
    .build()

val googleSignInClient = GoogleSignIn.getClient(this, gso)

2. 登录后获取Server Auth Code

登录成功后拿到serverAuthCode,把这个Code传到Firebase云函数(不要在客户端直接换token,因为需要Web Client Secret,这个不能暴露在客户端):

startActivityForResult(googleSignInClient.signInIntent, RC_SIGN_IN)

// 在onActivityResult里处理登录结果
val account = GoogleSignIn.getSignedInAccountFromIntent(data).result
val serverAuthCode = account.serverAuthCode // 将这个code传到云函数

云函数端处理Token并调用Calendar API

1. 用Server Auth Code换Access/Refresh Token

云函数里用收到的serverAuthCode,加上你的Web Client ID和Secret,去Google的OAuth2服务器换token:

async function exchangeAuthCodeForTokens(authCode) {
    const oauth2Client = new google.google.auth.OAuth2(
        // 从Firebase环境变量读取,不要硬编码敏感信息!
        functions.config().google.client_id,
        functions.config().google.client_secret
    );

    const { tokens } = await oauth2Client.getToken(authCode);
    // tokens里包含access_token、refresh_token、expiry_date等字段
    return tokens;
}

注意:用firebase functions:config:set google.client_id="你的Web Client ID" google.client_secret="你的Web Client Secret"设置环境变量,避免硬编码敏感信息。

2. 存储Refresh Token

把换回来的refresh_token存在Firebase数据库的用户节点下(比如users/{userId}/calendarTokens/refreshToken),之后每次调用Calendar API都用它获取有效access token。

3. 改进云函数的Auth Client

修改你的getOauthClient函数,用refresh token自动获取并刷新access token:

async function getOauthClient(refreshToken) {
    const oauth2Client = new google.google.auth.OAuth2(
        functions.config().google.client_id,
        functions.config().google.client_secret
    );

    // 设置refresh token
    oauth2Client.setCredentials({ refresh_token: refreshToken });

    try {
        // 自动刷新access token(如果已过期)
        const { credentials } = await oauth2Client.refreshAccessToken();
        oauth2Client.setCredentials(credentials);
        // 可选:把新的access token存回数据库,减少下次刷新操作
    } catch (err) {
        console.error("刷新Access Token失败:", err);
        throw err;
    }

    return oauth2Client;
}

4. 修改addEvent函数逻辑

现在云函数应该先从数据库取用户的refresh token,再创建Auth Client:

export const addEvent = functions.database
    .ref('users/{user}/calendarEvents/{eventId}')
    .onCreate(async (snapshot, context) => {
        const data = snapshot.val();
        const userId = context.params.user;

        // 从数据库获取用户的refresh token
        const tokenSnapshot = await admin.database()
            .ref(`users/${userId}/calendarTokens/refreshToken`)
            .once('value');
        const refreshToken = tokenSnapshot.val();

        if (!refreshToken) {
            throw new Error("用户未授权Calendar访问");
        }

        const authClient = await getOauthClient(refreshToken);
        const calendar = google.google.calendar('v3');

        try {
            await calendar.events.insert({
                auth: authClient,
                calendarId: 'primary',
                resource: getCalendarEvent(data),
            });
            // 成功后删除触发节点
            await snapshot.ref.remove();
            return;
        } catch (err) {
            console.error("添加Calendar事件失败:", err);
            throw err;
        }
    });

为什么之前的Token没用?

  • serverAuthCode:这是授权码,不是直接能用的access token,必须用它去换真正的token
  • idToken:这是Google身份验证Token,用来验证用户身份,不能访问Calendar API
  • FirebaseAuth.getIdToken():这是Firebase的身份Token,只适用于Firebase相关服务,和Google Calendar API无关

额外注意事项

  1. 确保在Google Cloud Console里启用了Google Calendar API
  2. 配置OAuth同意屏幕,添加https://www.googleapis.com/auth/calendar权限,测试阶段可以添加测试用户
  3. 第一次授权才能拿到refresh token,之后用户重新登录不会返回,所以一定要保存好
  4. 如果用户撤销了权限,refresh token会失效,需要引导用户重新授权

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 07:21:55