【问题标题】:Firebase cloud functions Appcheck for https.onRequestFirebase 云功能 Appcheck for https.onRequest
【发布时间】:2021-06-22 12:07:48
【问题描述】:

根据文档,我们可以如下添加 appcheck,

exports.yourCallableFunction = functions.https.onCall((data, context) => {
  // context.app will be undefined if the request doesn't include a valid
  // App Check token.
  if (context.app == undefined) {
    throw new functions.https.HttpsError(
        'failed-precondition',
        'The function must be called from an App Check verified app.')
  }
});

我现在的问题是如何为以下场景添加应用检查?

exports.date = functions.https.onRequest((req, res) => {

});

【问题讨论】:

    标签: firebase google-cloud-functions firebase-app-check


    【解决方案1】:

    在客户端中,从 Firebase 获取 appCheck 令牌。将其发送到您的函数的标题中。从 req 对象的标头中获取令牌。使用 firebase-admin 验证令牌。我将在下面包含客户端的文档,然后是我如何使用 Apollo-client graphql 在客户端实现它的要点。然后我将包含后端的文档,然后是我如何使用 Apollo 实现后端的要点。

    客户(来自文档):

    const { initializeAppCheck, getToken } = require('firebase/app-check');
    
    const appCheck = initializeAppCheck(
        app,
        { provider: provider } // ReCaptchaV3Provider or CustomProvider
    );
    
    const callApiWithAppCheckExample = async () => {
      let appCheckTokenResponse;
      try {
          appCheckTokenResponse = await getToken(appCheck, /* forceRefresh= */ false);
      } catch (err) {
          // Handle any errors if the token was not retrieved.
          return;
      }
    
      // Include the App Check token with requests to your server.
      const apiResponse = await fetch('https://yourbackend.example.com/yourApiEndpoint', {
          headers: {
              'X-Firebase-AppCheck': appCheckTokenResponse.token,
          }
      });
    
      // Handle response from your backend.
    }; 
    

    客户端(我的实现要点)

    import { setContext } from "@apollo/client/link/context";
    import { app } from '../firebase/setup';
    import { initializeAppCheck, ReCaptchaV3Provider, getToken } from "firebase/app-check"
    
    let appCheck
    let appCheckTokenResponse
    
    const getAppCheckToken = async () => {
      const appCheckTokenResponsePromise = await getToken(appCheck, /* forceRefresh= */ false)
      appCheckTokenResponse = appCheckTokenResponsePromise
    }
    
    const authLink = setContext(async (_, { headers }) => {
      if (typeof window !== "undefined" && process.env.NEXT_PUBLIC_ENV === 'production') {
        appCheck = initializeAppCheck(app, {
          provider: new ReCaptchaV3Provider('my_public_key_from_recaptcha_V3'),
          isTokenAutoRefreshEnabled: true
        })
        await getAppCheckToken()
      }
    
      return {
        headers: {
          ...headers,
          'X-Firebase-AppCheck': appCheckTokenResponse?.token,
        },
      }
    })
    

    后端/服务器(来自文档)

    const express = require('express');
    const app = express();
    
    const firebaseAdmin = require('firebase-admin');
    const firebaseApp = firebaseAdmin.initializeApp();
    
    const appCheckVerification = async (req, res, next) => {
        const appCheckToken = req.header('X-Firebase-AppCheck');
    
        if (!appCheckToken) {
            res.status(401);
            return next('Unauthorized');
        }
    
        try {
            const appCheckClaims = await firebaseAdmin.appCheck().verifyToken(appCheckToken);
    
            // If verifyToken() succeeds, continue with the next middleware
            // function in the stack.
            return next();
        } catch (err) {
            res.status(401);
            return next('Unauthorized');
        }
    }
    
    app.get('/yourApiEndpoint', [appCheckVerification], (req, res) => {
        // Handle request.
    });
    

    后端/服务器(我的实现要点)

    import { https } from 'firebase-functions'
    import gqlServer from './graphql/server'
    const functions = require('firebase-functions')
    
    const env = process.env.ENV || functions.config().config.env
    
    const server = gqlServer()
    
    const api = https.onRequest((req, res) => {
        server(req, res)
    })
    
    export { api }
    
    . . .
    
    
    import * as admin from 'firebase-admin';
    const functions = require('firebase-functions');
    
    const env = process.env.ENV || functions.config().config.env
    
    admin.initializeApp()
    
    
    appCheckVerification = async (req: any, res: any) => {
      const appCheckToken = req.header('X-Firebase-AppCheck')
      if (!appCheckToken) {
        return false
      }
    
      try {
        const appCheckClaims = await admin.appCheck().verifyToken(appCheckToken);
        return true
      } catch (error) {
        console.error(error)
        return false
      }
     }
    
    . . .
    
    
    const apolloServer = new ApolloServer({
      introspection: isDevelopment,
      typeDefs: schema,
      resolvers,
      context: async ({ req, res }) => {
                
        if (!isDevelopment && !isTest) {
          const appCheckVerification = await appCheckVerification(req, res)
            if (!appCheckVerification) throw Error('Something went wrong with verification')
     }
    return { req, res, }
    }
    

    【讨论】:

      【解决方案2】:

      如果您enforce app check in Cloud Functions,它将只允许来自您项目中注册的应用程序的调用。

      我不确定这是否足以满足您的用例,因为我怀疑您可以提供网络挂钩的大多数应用程序都已实现应用程序证明 - 这就是 App Check 识别有效请求的方式。

      【讨论】:

      • 拒绝来电是否也要收费??
      • @Noobdeveloper 当然,谷歌永远不会让你用完电......这样做的唯一好处是它可以帮助你避免长时间复杂的数据库查询或类似的事情,但功能通话、内存和数据传输费用仍然适用
      【解决方案3】:

      您可以在客户端生成应用检查令牌,并使用 Firebase 管理 SDK 在服务器中验证该令牌。 Here 是相同的 firebase 文档

      【讨论】:

      • 您能否提供文档中的示例?如果 URL 无效,这将有助于未来的用户。
      【解决方案4】:

      Firebase 启用应用检查强制文档告诉您,要从您的函数验证调用者,您只需要检查 context.app 然后为您提供这样的示例

      exports.EXAMPLE = functions.https.onCall((data, context) => {});
      

      https://firebase.google.com/docs/app-check/cloud-functions?authuser=0

      但是当你在谷歌云仪表板中部署你的函数时,你选择 HTTP FUNCTION -> nodejs 14 -> 然后你会被定向到这样的代码

      /**
       * Responds to any HTTP request.
       *
       * @param {!express:Request} req HTTP request context.
       * @param {!express:Response} res HTTP response context.
       */
      exports.helloWorld = (req, res) => {
        let message = req.query.message || req.body.message || 'Hello World!';
        res.status(200).send(message);
      };
      

      当我看到这个时,我的问题是:“如果我只有请求/响应,我将如何获得上下文”

      答案很简单。 您必须切换构造函数

      你必须重写你的函数,而不是像处理上下文/数据的任何快速函数那样处理 req/res

      http 函数不同于可调用函数(处理上下文/数据的函数)

      这是相似的,但不完全相等,因此需要进行一些修改。

      主要是如果您的函数处理异步内容并且响应延迟,您将需要重写许多内容

      查看本教程 https://firebase.google.com/docs/functions/callable

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-01
        • 2018-08-01
        • 2020-06-09
        • 2018-11-29
        • 2018-05-09
        • 2021-10-11
        • 2020-06-13
        相关资源
        最近更新 更多