Как защитить Firebase Cloud Function HTTP конечной точки, чтобы разрешить только пользователям, прошедшим проверку подлинности Firebase?

142

С новой облачной функцией firebase я решил переместить часть моей конечной точки HTTP на firebase. Все отлично работает ... Но у меня такая проблема. У меня две конечные точки, построенные с помощью триггеров HTTP (облачные функции)

  1. Конечная точка API для создания пользователей и возвращает пользовательский токен, созданный Firebase Admin SDK.
  2. Конечная точка API для получения определенных сведений о пользователе.

Хотя первая конечная точка в порядке, но для моей второй конечной точки я хотел бы защитить ее только для аутентифицированных пользователей. Имеется в виду тот, у кого есть токен, который я сгенерировал ранее.

Как мне решить эту проблему?

Я знаю, что мы можем получить параметры заголовка в облачной функции, используя

request.get('x-myheader')

но есть ли способ защитить конечную точку так же, как защиту базы данных в реальном времени?

spaceMonkey
источник
как вы получили пользовательский токен, сгенерированный Firebase Admin SDK в первом API
Амин Харбауи,
2
@AmineHarbaoui У меня был такой же вопрос. Смотрите эту страницу: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Ответы:

138

Есть официальный образец кода для того, что вы пытаетесь сделать. Он иллюстрирует, как настроить функцию HTTPS для запроса заголовка авторизации с токеном, который клиент получил во время аутентификации. Функция использует библиотеку firebase-admin для проверки токена.

Кроме того, вы можете использовать « вызываемые функции », чтобы упростить большую часть этого шаблона, если ваше приложение может использовать клиентские библиотеки Firebase.

Дуг Стивенсон
источник
2
Этот образец кода еще действителен? Вы по-прежнему решаете эту проблему сегодня?
Гал Браха
1
@GalBracha Он должен быть действителен и сегодня (31 октября 2017 г.).
Дуг Стивенсон
@DougStevenson, будут ли эти вызовы console.log иметь какое-либо «заметное» влияние на производительность?
Санка Даршана
1
Как использование вызываемых функций упростит создание шаблона? Насколько я понимаю, это просто серверные функции, не относящиеся к REST, я не совсем понимаю, как они здесь связаны. Спасибо.
1252748
2
@ 1252748 Если почитать связанную документацию, станет понятно. Он автоматически обрабатывает передачу и проверку токена аутентификации, поэтому вам не нужно писать этот код с обеих сторон.
Дуг Стивенсон
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
Вы можете подробнее рассказать о корсах?
Pete
@pete: cors просто решает проблему совместного использования ресурсов из разных источников. Вы можете узнать об этом больше в Google.
Lạng Hoàng
@pete Cors позволяет вам подключаться к этой конечной точке firebase-backend с разных URL-адресов.
Walter Monecke
6
@RezaRahmati. Вы можете использовать этот getIdToken()метод на стороне клиента (например firebase.auth().currentUser.getIdToken().then(token => console.log(token))) в документации firebase
Will
18

Как также упомянул @Doug, вы можете использовать вызываемые функции , чтобы исключить некоторый шаблонный код из вашего клиента и вашего сервера.

Вызываемая функция Exampale:

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

Вышеупомянутые методы аутентифицируют пользователя с помощью логики внутри функции, поэтому функция должна быть вызвана для выполнения проверки.

Это отличный метод, но для наглядности есть альтернатива:

Вы можете сделать функцию «частной», чтобы ее могли вызывать только зарегистрированные пользователи (разрешения выбираете вы). В этом случае неаутентифицированные запросы отклоняются вне контекста функции, и функция вообще не вызывается.

Здесь приведены ссылки на (а) настройку функций как общедоступных / частных , а затем (б) аутентификацию конечных пользователей для ваших функций .

Обратите внимание, что приведенные выше документы относятся к Google Cloud Platform, и это действительно работает, потому что каждый проект Firebase также является проектом GCP. Связанное с этим методом предостережение заключается в том, что на момент написания он работает только с аутентификацией на основе учетной записи Google.

Tedskovsky
источник
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

Я изо всех сил пытался получить правильную аутентификацию firebase в функции golang GCP. На самом деле для этого нет примера, поэтому я решил создать эту крошечную библиотеку: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Теперь вы можете легко аутентифицировать пользователей с помощью firebase-auth (который отличается от gcp-authenticated-functions и не поддерживается напрямую прокси-сервером с идентификацией).

Вот пример использования утилиты:

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

jblew
источник
0

В Firebase для упрощения вашего кода и вашей работы это всего лишь вопрос архитектурного дизайна :

  1. Для общедоступных сайтов / содержимого используйте триггеры HTTPS с расширениемExpress . Чтобы ограничить только тот же сайт или только определенный сайт , используйте CORSдля управления этим аспектом безопасности. Это имеет смысл, потому что Expressэто полезно для SEO из-за рендеринга контента на стороне сервера.
  2. Для приложений, требующих аутентификации пользователя , используйте HTTPS Callable Firebase Functions , а затем используйте contextпараметр, чтобы избавиться от неприятностей. Это также имеет смысл, потому что, например, одностраничное приложение, созданное с помощью AngularJS - AngularJS плох для SEO, но, поскольку это приложение, защищенное паролем, вам также не нужно много SEO. Что касается шаблонов, AngularJS имеет встроенные шаблоны, поэтому нет необходимости в отдельных шаблонах с Express. Тогда вызываемых функций Firebase должно быть достаточно.

С учетом вышесказанного, больше никаких хлопот и облегчение жизни.

Антонио Оои
источник