如何保护Firebase Cloud Function HTTP端点仅允许经过Firebase身份验证的用户?


141

借助新的firebase云功能,我决定将一些HTTP端点移至firebase。一切都很好...但是我有以下问题。我有两个通过HTTP触发器(云函数)构建的端点

  1. 用于创建用户并返回由Firebase Admin SDK生成的自定义令牌的API端点。
  2. API端点以获取某些用户详细信息。

虽然第一个端点很好,但是对于我的第二个端点,我只想为经过身份验证的用户保护它。表示拥有我之前生成的令牌的人。

我该如何解决呢?

我知道我们可以使用以下方法在云函数中获取Header参数

request.get('x-myheader')

但是有没有办法像保护实时数据库一样保护端点?


您如何在第一个API中获得由Firebase Admin SDK生成的自定义令牌
Amine Harbaoui,

2
@AmineHarbaoui我有同样的问题。看到这个页面:firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Answers:


137

您要执行的操作有一个官方代码示例。它说明了如何设置HTTPS功能,以要求带有客户端在身份验证期间接收到的令牌的Authorization标头。该函数使用firebase-admin库来验证令牌。

另外,如果您的应用程序能够使用Firebase客户端库,则可以使用“ 可调用函数 ”简化很多样板。


2
此代码示例仍然有效吗?您今天仍然会如何处理?
Gal Bracha

1
@GalBracha它应该在今天(2017年10月31日)仍然有效。
道格·史蒂文森

@DougStevenson的那些“ console.log”调用是否会对性能产生“显着”影响?
Sanka Darshana

1
使用可调用函数将如何使样板更加容易?据我了解,这些只是“非REST”服务器功能,我真的不太了解它们在这里的关系。谢谢。
1252748

2
@ 1252748如果阅读链接的文档,它将变得很清楚。它自动处理auth令牌的传递和验证,因此您不必在任何一侧编写该代码。
道格·史蒂文森

121

如@Doug所述,您可以firebase-admin用来验证令牌。我建立了一个简单的例子:

exports.auth = functions.https.onRequest((req, res) => {
  cors(req, res, () => {
    const tokenId = req.get('Authorization').split('Bearer ')[1];

    return admin.auth().verifyIdToken(tokenId)
      .then((decoded) => res.status(200).send(decoded))
      .catch((err) => res.status(401).send(err));
  });
});

在上面的示例中,我还启用了CORS,但这是可选的。首先,您获得Authorization标题并找出token

然后,您可以firebase-admin用来验证该令牌。您将在响应中获得该用户的解码信息。否则,如果令牌无效,则会引发错误。


13
因为它很简单,所以被支持,并且不像官方示例那样依赖于表达。
DarkNeuron

5
您能解释更多关于cors的信息吗?
皮特

@pete:cors只是在解决跨域资源共享。您可以在Google上了解更多有关它的信息。
郎晃

@pete Cors允许您从不同的URL到达该Firebase后端端点。
Walter Monecke

6
@RezaRahmati您可以使用getIdToken()方法上的客户端(例如firebase.auth().currentUser.getIdToken().then(token => console.log(token))火力文档
威尔

18

就像@Doug提到的那样,您可以使用Callable Functions来从客户端和服务器中排除一些样板代码

实例调用函数:

export const getData = functions.https.onCall((data, context) => {
  // verify Firebase Auth ID token
  if (!context.auth) {
    return { message: 'Authentication Required!', code: 401 };
  }

  // do your things..
  const uid = context.auth.uid;
  const query = data.query;

  return { message: 'Some Data', code: 400 };
});

可以像这样直接从您的客户端调用它:

firebase.functions().httpsCallable('getData')({query}).then(result => console.log(result));

2

上述方法使用函数内部的逻辑对用户进行身份验证,因此必须仍然调用该函数以进行检查。

这是一种非常好的方法,但是为了全面理解,有一种替代方法:

您可以设置一个功能是“私人”,以便它不能只是由注册用户(您决定权限)被调用。在这种情况下,未经身份验证的请求将在函数上下文之外被拒绝,并且根本不会调用该函数。

这里是对(a)将功能配置为public / private,然后(b)对最终用户进行功能验证的参考

请注意,上面的文档适用于Google Cloud Platform,并且确实如此,因为每个Firebase项目也是 GCP项目。与该方法相关的警告是,截至撰写时,它仅适用于基于Google帐户的身份验证。


1

有一个使用Express的不错的官方示例-将来可能会派上用场:https : //github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js(粘贴在下面)当然)

请记住,exports.app使您的函数在/appslug下可用(在这种情况下,只有一个函数并且在<you-firebase-app>/app/hello。下可用)。要摆脱它,您实际上需要重写Express部分(用于验证的中间件保持不变-它非常有效好,由于评论,这是可以理解的。

/**
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
'use strict';

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const express = require('express');
const cookieParser = require('cookie-parser')();
const cors = require('cors')({origin: true});
const app = express();

// Express middleware that validates Firebase ID Tokens passed in the Authorization HTTP header.
// The Firebase ID token needs to be passed as a Bearer token in the Authorization HTTP header like this:
// `Authorization: Bearer <Firebase ID Token>`.
// when decoded successfully, the ID Token content will be added as `req.user`.
const validateFirebaseIdToken = async (req, res, next) => {
  console.log('Check if request is authorized with Firebase ID token');

  if ((!req.headers.authorization || !req.headers.authorization.startsWith('Bearer ')) &&
      !(req.cookies && req.cookies.__session)) {
    console.error('No Firebase ID token was passed as a Bearer token in the Authorization header.',
        'Make sure you authorize your request by providing the following HTTP header:',
        'Authorization: Bearer <Firebase ID Token>',
        'or by passing a "__session" cookie.');
    res.status(403).send('Unauthorized');
    return;
  }

  let idToken;
  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) {
    console.log('Found "Authorization" header');
    // Read the ID Token from the Authorization header.
    idToken = req.headers.authorization.split('Bearer ')[1];
  } else if(req.cookies) {
    console.log('Found "__session" cookie');
    // Read the ID Token from cookie.
    idToken = req.cookies.__session;
  } else {
    // No cookie
    res.status(403).send('Unauthorized');
    return;
  }

  try {
    const decodedIdToken = await admin.auth().verifyIdToken(idToken);
    console.log('ID Token correctly decoded', decodedIdToken);
    req.user = decodedIdToken;
    next();
    return;
  } catch (error) {
    console.error('Error while verifying Firebase ID token:', error);
    res.status(403).send('Unauthorized');
    return;
  }
};

app.use(cors);
app.use(cookieParser);
app.use(validateFirebaseIdToken);
app.get('/hello', (req, res) => {
  res.send(`Hello ${req.user.name}`);
});

// This HTTPS endpoint can only be accessed by your Firebase Users.
// Requests need to be authorized by providing an `Authorization` HTTP header
// with value `Bearer <Firebase ID Token>`.
exports.app = functions.https.onRequest(app);

我重写以摆脱/app

const hello = functions.https.onRequest((request, response) => {
  res.send(`Hello ${req.user.name}`);
})

module.exports = {
  hello
}

0

我一直在努力在golang GCP功能中获得正确的Firebase身份验证。实际上没有任何示例,因此我决定构建这个小库:https : //github.com/Jblew/go-firebase-auth-in-gcp-functions

现在,您可以使用firebase-auth(这与gcp-authenticated-functions不同,并且不受Identity-aware-proxy的直接支持)轻松地对用户进行身份验证。

这是使用该实用程序的示例:

import (
  firebaseGcpAuth "github.com/Jblew/go-firebase-auth-in-gcp-functions"
  auth "firebase.google.com/go/auth"
)

func SomeGCPHttpCloudFunction(w http.ResponseWriter, req *http.Request) error {
   // You need to provide 1. Context, 2. request, 3. firebase auth client
  var client *auth.Client
    firebaseUser, err := firebaseGcpAuth.AuthenticateFirebaseUser(context.Background(), req, authClient)
    if err != nil {
    return err // Error if not authenticated or bearer token invalid
  }

  // Returned value: *auth.UserRecord
}

只需记住使用--allow-unauthenticatedflag 部署函数(因为firebase身份验证发生在函数执行内部)。

希望这对您有所帮助,对我有帮助。由于性能原因,我决定将golang用于云功能 —Jędrzej


0

在Firebase中,为了简化您的代码和工作,这只是体系结构设计的问题

  1. 对于公共可访问的网站/内容请将HTTPS触发器与一起使用Express。要仅限制同一站点特定站点,请使用CORS来控制此方面的安全性。这是有道理的,因为Express其服务器端呈现内容对于SEO很有用。
  2. 对于需要用户身份验证的应用程序,请使用HTTPS可调用Firebase函数,然后使用context参数保存所有麻烦。这也是有道理的,因为例如使用AngularJS构建的单页应用程序-AngularJS对SEO不利,但是由于它是受密码保护的应用程序,因此您也不需要太多SEO。至于模板,AngularJS具有内置模板,因此不需要带有的服务器端模板Express。然后,Firebase可调用函数应该足够好。

考虑到以上几点,不再麻烦,让生活更轻松。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.