提问者:小点点

谷歌OAuth刷新令牌不返回有效访问令牌


我有一个Firebase应用程序,它对用户进行身份验证并返回一个访问令牌,然后我可以使用该令牌访问Google Calendar和Sheets API。我还保存了刷新令牌。已验证令牌的示例代码:

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小时后,访问令牌过期。Firebase auth在登录后为用户对象提供刷新令牌

我使用该刷新令牌重新验证,并通过发布到以下地址获得新的访问令牌:

https://securetoken.googleapis.com/v1/token?key=firebaseAppAPIKey

新的访问令牌不再适用于GoogleAPI,也不再具有授权范围。我也试着把它发送到

https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=“刷新令牌”

它给我错误“无效令牌”。当我使用firebase的原始令牌时,它工作正常。

还有谁遇到类似的问题吗?我还没有想出一种方法来使用正确的访问范围刷新原始访问令牌,而不会让用户再次注销和登录。

谢谢!


共1个答案

匿名用户

经过多次尝试,我终于能够解决它。

在介质上发布详细的解决方案:https://inaguirre.medium.com/reusing-access-tokens-in-firebase-with-react-and-node-3fde1d48cbd3

在客户机上,我使用了React和Firebase库,在服务器上,我使用了Node。js与google API包和firebase admin skd包链接到同一firebase项目。

步骤:

  1. (CLIENT)向服务器发送请求以生成认证链接
  2. (SERVER)生成Auth Link并使用googleapis的getAuthLink()将其发送回客户端。使用谷歌登录并处理重定向。
  3. (SERVER)在重定向路由上,使用查询字符串上的Google代码来验证用户并获取其用户凭据。使用这些凭据检查用户是否已在Firebase上注册。
  4. (SERVER)如果用户注册,获取访问权限并使用oauth2.get令牌(代码)刷新令牌,更新数据库中用户配置文件上的刷新令牌。如果用户未注册,则使用firebase.createUser()创建新用户,并使用刷新令牌在数据库上创建用户配置文件。
  5. (SERVER)使用firebase.createCustomToken(userId)将id_token发送回客户端并进行身份验证。
  6. (SERVER)使用res.redirect({access_token,referesh_token,id_token})将凭据发送回客户端。
  7. (CLIENT)在客户端上,使用SignInBackCustomToken(id_token)进行身份验证,也重构查询以获得access_tokenrefresh_token发送API调用。
  8. (CLIENT)设置访问令牌的到期日期。在每个请求中,检查当前日期是否高于到期日期。如果是,请请求新令牌以https://www.googleapis.com/oauth2/v4/token刷新令牌。否则使用存储的access_token。

大多数事情发生在身份验证后处理Google重定向时。下面是一个在后端处理身份验证和令牌的示例:

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;