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

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

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

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

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

request.get('x-myheader')

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

Ответы (9)

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

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

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

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

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

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

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

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

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

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

импорт (
  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 {
   // Вам необходимо предоставить 1. Контекст, 2. Запрос, 3. Клиент аутентификации firebase
  var client * auth.Client
    firebaseUser, err: = firebaseGcpAuth.AuthenticateFirebaseUser (context.Background (), req, authClient)
    if err! = nil {
    return err // Ошибка, если не аутентифицирован или токен-носитель недействителен
  }

  // Возвращаемое значение: * auth.UserRecord
}

Просто не забывайте развертывать свою функцию с флагом - allow-unauthenticated (поскольку аутентификация firebase происходит внутри выполнения функции).

Надеюсь, это поможет вам, как помогло мне. Я решил использовать golang для облачных функций по соображениям производительности - Jędrzej

Здесь много полезной информации, которая действительно мне помогла, но я подумал, что было бы неплохо разобрать простой рабочий пример для тех, кто использует Angular и пытается это сделать впервые. Документацию Google Firebase можно найти по адресу https://firebase.google.com/docs/auth/admin/verify-id-tokens#web.

//#### 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 {

    //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(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
})

Есть хороший официальный пример использования Express - может пригодиться в будущем: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (вставлено ниже точно)

Имейте в виду, что exports.app делает ваши функции доступными под / app slug (в этом случае есть только одна функция и доступна под / 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 `.
// 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 ',
        '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 `.
exports.app = functions.https.onRequest(app);

Моя перезапись, чтобы избавиться от / app:

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

module.exports = {
  hello
}

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

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

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

Как упоминал @Doug, вы можете использовать firebase-admin для проверки токена. Я создал быстрый пример:

exports.auth = functions.https.onRequest ((req, res) => {
  cors (req, res, () => {
    const tokenId = req.get ('Авторизация'). split ('Носитель') [1];
    
    вернуть admin.auth (). verifyIdToken (tokenId)
      .then ((декодировано) => res.status (200) .send (декодировано))
      .catch ((err) => res.status (401) .send (err));
  });
});

В приведенном выше примере я также включил CORS, но это необязательно. Сначала вы получаете заголовок Authorization и обнаруживаете токен .

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

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

Пример вызываемой функции:

экспорт const getData = functions.https.onCall ((data, context) => {
  // проверяем токен Firebase Auth ID
  if (! context.auth) {
    return {message: 'Требуется аутентификация!', код: 401};
  }

  // делай свои дела ..
  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));

Вы можете принять это как логическое значение, возвращаемое функцией. Если пользователь подтвердил или нет, вы продолжите или остановите свой API. Кроме того, вы можете вернуть претензии или пользовательский результат с помощью переменной decode

const authenticateIdToken = async (
    req: functions.https.Request,
    res: functions.Response
) => {
    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;
    }
}

2022 WebDevInsider