190

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

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

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

我该如何解决这个问题?

我知道我们可以使用云函数中的 Header 参数

request.get('x-myheader')

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

4

9 回答 9

181

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

此外,如果您的应用程序能够使用 Firebase 客户端库,您可以使用“可调用函数”来简化这些样板文件。

于 2017-03-12T20:14:18.513 回答
159

正如@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来验证该令牌。您将在响应中获得该用户的解码信息。否则,如果令牌无效,则会引发错误。

于 2017-06-12T13:23:05.573 回答
39

正如@Doug 所提到的,您可以使用可调用函数来从您的客户端和服务器中排除一些样板代码。

可调用函数示例:

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));
于 2019-07-01T14:40:08.050 回答
8

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

这是一个完全好的方法,但为了全面起见,还有另一种方法:

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

以下是对 (a) 将功能配置为 public/private,然后 (b)验证最终用户对您的功能的参考。

请注意,上面的文档是针对 Google Cloud Platform 的,实际上,这是有效的,因为每个 Firebase 项目也是一个 GCP 项目。与此方法相关的一个警告是,在撰写本文时,它仅适用于基于 Google 帐户的身份验证。

于 2020-02-03T16:10:46.553 回答
2

这里有很多对我很有帮助的重要信息,但我认为为第一次尝试使用 Angular 的任何人分解一个简单的工作示例可能会很好。可以在https://firebase.google.com/docs/auth/admin/verify-id-tokens#web找到 Google Firebase 文档。

//#### YOUR TS COMPONENT FILE #####
import { Component, OnInit} from '@angular/core';
import * as firebase from 'firebase/app';
import { YourService } from '../services/yourservice.service';

@Component({
  selector: 'app-example',
  templateUrl: './app-example.html',
  styleUrls: ['./app-example.scss']
})

export class AuthTokenExample implements OnInit {

//property
idToken: string;

//Add your service
constructor(private service: YourService) {}

ngOnInit() {

    //get the user token from firebase auth
    firebase.auth().currentUser.getIdToken(true).then((idTokenData) => {
        //assign the token to the property
        this.idToken = idTokenData;
        //call your http service upon ASYNC return of the token
        this.service.myHttpPost(data, this.idToken).subscribe(returningdata => {
            console.log(returningdata)
        });

    }).catch((error) => {
        // Handle error
        console.log(error);
    });

  }

}

//#### YOUR SERVICE #####
//import of http service
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})

export class MyServiceClass {

    constructor(private http: HttpClient) { }

  //your myHttpPost method your calling from your ts file
  myHttpPost(data: object, token: string): Observable<any> {

    //defining your header - token is added to Authorization Bearer key with space between Bearer, so it can be split in your Google Cloud Function
    let httpOptions = {
        headers: new HttpHeaders({
            'Content-Type': 'application/json',
         'Authorization': 'Bearer ' + token
        })
    }

    //define your Google Cloud Function end point your get from creating your GCF
    const endPoint = ' https://us-central1-your-app.cloudfunctions.net/doSomethingCool';

    return this.http.post<string>(endPoint, data, httpOptions);

  }

}


//#### YOUR GOOGLE CLOUD FUNCTION 'GCF' #####
//your imports
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cors = require('cors')({origin: true});


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

//cross origin middleware
    cors(req, res, () => {

        //get the token from the service header by splitting the Bearer in the Authorization header 
        const tokenId = req.get('Authorization').split('Bearer ')[1];

        //verify the authenticity of token of the user
        admin.auth().verifyIdToken(tokenId)
            .then((decodedToken) => {
                //get the user uid if you need it.
               const uid = decodedToken.uid;

                //do your cool stuff that requires authentication of the user here.

            //end of authorization
            })
            .catch((error) => {
                console.log(error);
            });

    //end of cors
    })

//end of function
})
于 2021-06-23T16:58:25.247 回答
1

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

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

考虑到上述情况,不再有麻烦,让生活更轻松。

于 2020-06-25T08:53:19.343 回答
0

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

请记住,这exports.app使您的函数在/appslug 下可用(在这种情况下,只有一个函数并且在<you-firebase-app>/app/hello.很好,并且由于评论而很容易理解)。

/**
 * 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
}
于 2020-02-28T22:28:16.427 回答
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-unauthenticated标志部署函数(因为 firebase 身份验证发生在函数执行内部)。

希望这会帮助你,因为它帮助了我。出于性能原因,我决定将 golang 用于云功能— Jędrzej

于 2020-06-22T14:35:42.677 回答
0

您可以将此作为函数返回布尔值。如果用户验证与否,那么您将继续或停止您的 API。此外,您可以从变量 decode 返回声明或用户结果

const authenticateIdToken = async (
    req: functions.https.Request,
    res: functions.Response<any>
) => {
    try {
        const authorization = req.get('Authorization');
        if (!authorization) {
            res.status(400).send('Not Authorized User');
            return false;
        }
        const tokenId = authorization.split('Bearer ')[1];

        return await auth().verifyIdToken(tokenId)
            .then((decoded) => {
                return true;
            })
            .catch((err) => {
                res.status(401).send('Not Authorized User')
                return false;
            });
    } catch (e) {
        res.status(400).send('Not Authorized User')
        return false;
    }
}
于 2020-12-14T14:33:48.650 回答