firebase Google OAuth刷新令牌未返回有效的访问令牌

yhqotfr8  于 2023-01-05  发布在  Go
关注(0)|答案(2)|浏览(303)

我有一个Firebase应用程序,它验证用户身份并返回一个访问令牌,然后我可以使用该令牌访问Google Calendar and Sheets API。我还保存了refreshToken。

firebase
  .signInWithGoogle()
  .then(async (socialAuthUser) => { 
    let accessToken = socialAuthUser.credential.accessToken // token to access Google Sheets API
    let refreshToken = socialAuthUser.user.refreshToken
    this.setState({accessToken, refreshToken}) 
 })

1小时后,accessToken将过期。登录后,Firebase auth将在用户对象上提供刷新令牌
我使用该刷新令牌重新进行身份验证,并通过发布到以下地址获得新的access_token:
https://securetoken.googleapis.com/v1/token?key=firebaseAppAPIKey
这个新的访问令牌不再适用于Google API,它也不再具有授权范围。
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token="refreshToken"
它给我错误“无效的令牌”。当我使用来自FireBase的原始令牌时,它工作得很好。
还有其他人遇到类似的问题吗?我还没有找到一种方法来刷新具有正确访问作用域的原始访问令牌,而无需用户再次注销和登录。
谢谢!

wvmv3b1j

wvmv3b1j1#

经过多次尝试,我终于能够解决它。
在Medium上发布了详细的解决方案:https://inaguirre.medium.com/reusing-access-tokens-in-firebase-with-react-and-node-3fde1d48cbd3
在客户机上,我使用了带有Firebase库的React,在服务器上,我使用了Node.js,其中包含链接到同一个Firebase项目的google-apis包和firebase-admin skd包。

    • 步骤:**

1.(CLIENT)向服务器发送请求以生成身份验证链接
1.(服务器)使用googleapis的getAuthLink()生成验证链接并将其发送回客户端。登录Google并处理重定向。
1.(服务器)在重定向路由中,使用Google提供的代码对用户进行身份验证,并获取其用户凭据。使用这些凭据检查用户是否已在Firebase上注册。
1.(SERVER)如果用户已注册,请使用oauth2.getTokens(代码)获取访问和刷新令牌,更新数据库中用户配置文件的刷新令牌。如果用户未注册,请使用firebase. createUser()创建新用户,同时使用刷新令牌在数据库中创建用户配置文件。
1.(服务器)使用firebase. createCustomToken(userId)将id_token发送回客户端并进行身份验证。
1.(服务器)使用资源重定向({access_token,referesh_token,id_token})将凭据发送回客户端。
1.(客户端)在客户端上,使用signInWithCustomToken(id_token)进行身份验证,同时重构查询以获取access_token和refresh_token,从而发送API调用。
1.(CLIENT)设置访问令牌的到期日期。在每次请求时,检查当前日期是否晚于到期日期。如果是,则使用刷新令牌向https://www.googleapis.com/oauth2/v4/token请求新令牌。否则,使用存储的access_token。
大多数事情发生在身份验证后处理Google Redirect时。下面是一个在后端处理auth和token的示例:

const router = require("express").Router();

const { google } = require("googleapis");

const { initializeApp, cert } = require("firebase-admin/app");

const { getAuth } = require("firebase-admin/auth");
const { getDatabase } = require("firebase-admin/database");
const serviceAccount = require("../google-credentials.json");

const fetch = require("node-fetch");

initializeApp({
  credential: cert(serviceAccount),
  databaseURL: "YOUR_DB_URL",
});

const db = getDatabase();

const oauth2Client = new google.auth.OAuth2(
  process.env.GOOGLE_CLIENT_ID,
  process.env.GOOGLE_CLIENT_SECRET,
  "http://localhost:8080/handleGoogleRedirect"
);

//post to google auth api to generate auth link
router.post("/authLink", (req, res) => {
  try {
    // generate a url that asks permissions for Blogger and Google Calendar scopes
    const scopes = [
      "profile",
      "email",
      "https://www.googleapis.com/auth/drive.file",
      "https://www.googleapis.com/auth/calendar",
    ];

    const url = oauth2Client.generateAuthUrl({
      access_type: "offline",
      scope: scopes,
      // force access
      prompt: "consent",
    });
    res.json({ authLink: url });
  } catch (error) {
    res.json({ error: error.message });
  }
});

router.get("/handleGoogleRedirect", async (req, res) => {
  console.log("google.js 39 | handling redirect", req.query.code);
  // handle user login
  try {
    const { tokens } = await oauth2Client.getToken(req.query.code);
    oauth2Client.setCredentials(tokens);

    // get google user profile info
    const oauth2 = google.oauth2({
      version: "v2",
      auth: oauth2Client,
    });

    const googleUserInfo = await oauth2.userinfo.get();

    console.log("google.js 72 | credentials", tokens);

    const userRecord = await checkForUserRecord(googleUserInfo.data.email);

    if (userRecord === "auth/user-not-found") {
      const userRecord = await createNewUser(
        googleUserInfo.data,
        tokens.refresh_token
      );
      const customToken = await getAuth().createCustomToken(userRecord.uid);
      res.redirect(
        `http://localhost:3000/home?id_token=${customToken}&accessToken=${tokens.access_token}&userId=${userRecord.uid}`
      );
    } else {
      const customToken = await getAuth().createCustomToken(userRecord.uid);

      await addRefreshTokenToUserInDatabase(userRecord, tokens);

      res.redirect(
        `http://localhost:3000/home?id_token=${customToken}&accessToken=${tokens.access_token}&userId=${userRecord.uid}`
      );
    }
  } catch (error) {
    res.json({ error: error.message });
  }
});

const checkForUserRecord = async (email) => {
  try {
    const userRecord = await getAuth().getUserByEmail(email);
    console.log("google.js 35 | userRecord", userRecord.displayName);
    return userRecord;
  } catch (error) {
    return error.code;
  }
};

const createNewUser = async (googleUserInfo, refreshToken) => {
  console.log(
    "google.js 65 | creating new user",
    googleUserInfo.email,
    refreshToken
  );
  try {
    const userRecord = await getAuth().createUser({
      email: googleUserInfo.email,
      displayName: googleUserInfo.name,
      providerToLink: "google.com",
    });

    console.log("google.js 72 | user record created", userRecord.uid);

    await db.ref(`users/${userRecord.uid}`).set({
      email: googleUserInfo.email,
      displayName: googleUserInfo.name,
      provider: "google",
      refresh_token: refreshToken,
    });

    return userRecord;
  } catch (error) {
    return error.code;
  }
};

const addRefreshTokenToUserInDatabase = async (userRecord, tokens) => {
  console.log(
    "google.js 144 | adding refresh token to user in database",
    userRecord.uid,
    tokens
  );
  try {
    const addRefreshTokenToUser = await db
      .ref(`users/${userRecord.uid}`)
      .update({
        refresh_token: tokens.refresh_token,
      });
    console.log("google.js 55 | addRefreshTokenToUser", tokens);
    return addRefreshTokenToUser;
  } catch (error) {
    console.log("google.js 158 | error", error);
    return error.code;
  }
};

router.post("/getNewAccessToken", async (req, res) => {
  console.log("google.js 153 | refreshtoken", req.body.refresh_token);

  // get new access token
  try {
    const request = await fetch("https://www.googleapis.com/oauth2/v4/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        client_id: process.env.GOOGLE_CLIENT_ID,
        client_secret: process.env.GOOGLE_CLIENT_SECRET,
        refresh_token: req.body.refresh_token,
        grant_type: "refresh_token",
      }),
    });
    const data = await request.json();
    console.log("google.js 160 | data", data);
    res.json({
      token: data.access_token,
    });
  } catch (error) {
    console.log("google.js 155 | error", error);
    res.json({ error: error.message });
  }
});

module.exports = router;
9jyewag0

9jyewag02#

对于任何现在遇到这个的人来说,在这一点上有一个简单得多的方法。
我可以通过实现一个阻塞函数来解决这个问题,这个函数只是将refreshToken和exiry日期保存到firestore,然后您可以从前端查询这个函数来获取令牌。
确保在firebase设置中启用refreshToken,否则阻塞函数将无法访问它。
https://firebase.google.com/docs/auth/extend-with-blocking-functions

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import {
  AuthEventContext,
  AuthUserRecord,
} from "firebase-functions/lib/common/providers/identity";

admin.initializeApp();

exports.beforeSignIn = functions.auth
  .user()
  .beforeSignIn((user: AuthUserRecord, context: AuthEventContext) => {
    // If the user is created by Yahoo, save the access token and refresh token
    if (context.credential?.providerId === "yahoo.com") {
      const db = admin.firestore();

      const uid = user.uid;
      const data = {
        accessToken: context.credential.accessToken,
        refreshToken: context.credential.refreshToken,
        tokenExpirationTime: context.credential.expirationTime,
      };

      // set will add or overwrite the data
      db.collection("users").doc(uid).set(data);
    }
  });

相关问题