Як захистити кінцеву точку HTTP-функції Firebase Cloud Function, щоб дозволити лише користувачам, що мають автентифікацію Firebase?


141

Завдяки новій хмарній функції firebase я вирішив перемістити частину моєї кінцевої точки HTTP до firebase. Все чудово працює ... Але у мене є таке питання. У мене є дві кінцеві точки, побудовані за допомогою тригерів HTTP (хмарні функції)

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

Хоча перша кінцева точка є нормальною, але для моєї другої кінцевої точки я б хотів захистити її лише для аутентифікованих користувачів. мається на увазі той, хто має маркер, який я генерував раніше.

Як я можу вирішити це?

Я знаю, що ми можемо отримати параметри заголовка у хмарній функції за допомогою

request.get('x-myheader')

але чи існує спосіб захисту кінцевої точки так само, як захист бази даних у режимі реального часу?


як ви отримали користувацький маркер, сформований SDK Firebase Admin у першому API
Amine Harbaoui

2
@AmineHarbaoui У мене було те саме питання. Дивіться цю сторінку: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Відповіді:


137

Існує офіційний зразок коду для того, що ви намагаєтеся зробити. Це ілюструє, як налаштувати свою функцію 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: cors просто вирішує спільний розподіл ресурсів. Ви можете в google дізнатися більше про це.
Lạng Hoàng

@pete Cors дозволяє потрапити на цю кінцеву точку вогневої бази з різних URL-адрес.
Вальтер Монеке

6
@RezaRahmati Метод можна використовувати на getIdToken()базі клієнтів (напр. firebase.auth().currentUser.getIdToken().then(token => console.log(token))) Документи firebase
буде

18

Як також згадував @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));

2

Вищеописані методи аутентифікують користувача, використовуючи логіку всередині функції, тому функцію потрібно все-таки викликати для перевірки.

Це абсолютно чудовий метод, але задля всеосяжності є альтернатива:

Ви можете встановити функцію "приватної", щоб її не можна було викликати, за винятком зареєстрованих користувачів (ви вирішили визначити дозволи). У цьому випадку неавторизовані запити відхиляються поза контекстом функції, а функція взагалі не викликається.

Тут наведено посилання на (a) Налаштування функцій як публічних / приватних , а потім (b) автентифікація кінцевих користувачів на ваші функції .

Зауважте, що наведені вище документи є для Cloud 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

Я намагаюся отримати належну аутентифікацію Firebase в функції GCP golang. Прикладу для цього насправді немає, тому я вирішив створити цю крихітну бібліотеку: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

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

Ось приклад використання утиліти:

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 відбувається всередині виконання функції).

Сподіваюся, що це допоможе вам, як і мені. Я вирішив використовувати голонг для хмарних функцій з міркувань продуктивності - Jędrzej


0

У Firebase, щоб спростити код і свою роботу, це лише питання архітектурного дизайну :

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

Зважаючи на вищезазначене, більше не виникає клопотів та полегшує життя.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.