Wie schütze ich den HTTP-Endpunkt der Firebase Cloud-Funktion, um nur Firebase-authentifizierte Benutzer zuzulassen?

140

Mit der neuen Firebase-Cloud-Funktion habe ich beschlossen, einen Teil meines HTTP-Endpunkts auf Firebase zu verschieben. Alles funktioniert super ... Aber ich habe das folgende Problem. Ich habe zwei Endpunkte, die von HTTP-Triggern (Cloud-Funktionen) erstellt wurden.

  1. Ein API-Endpunkt zum Erstellen von Benutzern und zum Zurückgeben des vom Firebase Admin SDK generierten benutzerdefinierten Tokens.
  2. Ein API-Endpunkt zum Abrufen bestimmter Benutzerdetails.

Der erste Endpunkt ist zwar in Ordnung, aber für meinen zweiten Endpunkt möchte ich ihn nur für authentifizierte Benutzer schützen. Das heißt, jemand, der das Token hat, das ich zuvor generiert habe.

Wie löse ich das?

Ich weiß, dass wir die Header-Parameter in der Cloud-Funktion mit abrufen können

request.get('x-myheader')

Aber gibt es eine Möglichkeit, den Endpunkt genauso zu schützen wie die Echtzeitdatenbank?

spaceMonkey
quelle
Wie haben Sie das benutzerdefinierte Token erhalten, das vom Firebase Admin SDK in der ersten API generiert wurde
Amine Harbaoui
2
@AmineHarbaoui Ich hatte die gleiche Frage. Siehe diese Seite: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Antworten:

137

Es gibt ein offizielles Codebeispiel für das, was Sie tun möchten. Es wird veranschaulicht, wie Sie Ihre HTTPS-Funktion so einrichten, dass ein Autorisierungsheader mit dem Token erforderlich ist, das der Client während der Authentifizierung erhalten hat. Die Funktion verwendet die Firebase-Admin-Bibliothek, um das Token zu überprüfen.

Sie können auch " aufrufbare Funktionen " verwenden, um einen Großteil dieser Boilerplate zu vereinfachen, wenn Ihre App Firebase-Clientbibliotheken verwenden kann.

Doug Stevenson
quelle
2
Ist dieses Codebeispiel noch gültig? Würden Sie das heute noch so angehen?
Gal Bracha
1
@ GalBracha Es sollte noch heute (31. Oktober 2017) gültig sein.
Doug Stevenson
@DougStevenson Werden diese Aufrufe von 'console.log' einen spürbaren Einfluss auf die Leistung haben?
Sanka Darshana
1
Wie erleichtert die Verwendung aufrufbarer Funktionen das Boilerplate? Soweit ich weiß, handelt es sich nur um "Nicht-REST" -Serverfunktionen, verstehe ich nicht wirklich, wie sie sich hier verhalten. Vielen Dank.
1252748
2
@ 1252748 Wenn Sie die verknüpfte Dokumentation lesen, wird dies deutlich. Die Übergabe und Validierung des Authentifizierungstokens erfolgt automatisch, sodass Sie diesen Code nicht auf beiden Seiten schreiben müssen.
Doug Stevenson
120

Wie von @Doug erwähnt, können Sie firebase-adminein Token überprüfen. Ich habe ein kurzes Beispiel aufgestellt:

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));
  });
});

Im obigen Beispiel habe ich auch CORS aktiviert, aber das ist optional. Zuerst erhalten Sie den AuthorizationHeader und finden das heraus token.

Anschließend können Sie firebase-admindieses Token überprüfen. In der Antwort erhalten Sie die dekodierten Informationen für diesen Benutzer. Andernfalls wird ein Fehler ausgegeben, wenn das Token nicht gültig ist.

Wille
quelle
13
Upvoted, da es einfach ist und nicht wie im offiziellen Beispiel vom Express abhängt.
DarkNeuron
5
Können Sie mehr über die Cors erklären?
Pete
@pete: cors löst nur die gemeinsame Nutzung von Ressourcen zwischen verschiedenen Ursprüngen. Sie können googeln, um mehr darüber zu erfahren.
Lạng Hoàng
Mit @pete Cors können Sie diesen Endpunkt des Firebase-Backends von verschiedenen URLs aus erreichen.
Walter Monecke
6
@RezaRahmati Sie können die getIdToken()Methode auf clientseitigen (z. B. firebase.auth().currentUser.getIdToken().then(token => console.log(token))) Firebase-Dokumenten verwenden
Will
18

Wie auch von @Doug erwähnt, können Sie Callable Functions verwenden, um Boilerplate-Code von Ihrem Client und Ihrem Server auszuschließen .

Aufrufbare Exampale-Funktion:

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 };
});

Es kann wie folgt direkt von Ihrem Client aufgerufen werden:

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

Die oben genannten Methoden authentifizieren den Benutzer mithilfe der Logik innerhalb der Funktion. Daher muss die Funktion weiterhin aufgerufen werden, um die Überprüfung durchzuführen.

Das ist eine völlig gute Methode, aber der Vollständigkeit halber gibt es eine Alternative:

Sie können eine Funktion als „privat“ gesetzt , so dass es nicht kann , außer durch registrierte Benutzer aufgerufen werden (Sie entscheiden , auf Berechtigungen). In diesem Fall werden nicht authentifizierte Anforderungen außerhalb des Kontextes der Funktion abgelehnt, und die Funktion wird überhaupt nicht aufgerufen.

Hier finden Sie Verweise auf (a) Konfigurieren von Funktionen als öffentlich / privat und (b) Authentifizieren von Endbenutzern für Ihre Funktionen .

Beachten Sie, dass die oben genannten Dokumente für die Google Cloud Platform gelten. Dies funktioniert in der Tat, da jedes Firebase-Projekt auch ein GCP-Projekt ist. Eine verwandte Einschränkung bei dieser Methode ist, dass sie zum Zeitpunkt des Schreibens nur mit der auf einem Google-Konto basierenden Authentifizierung funktioniert.

Tedskovsky
quelle
1

Es gibt ein schönes offizielles Beispiel für die Verwendung von Express - möglicherweise in Zukunft nützlich: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (unten eingefügt) sicher)

Denken Sie daran, exports.appdass Ihre Funktionen unter /appslug verfügbar sind (in diesem Fall gibt es nur eine Funktion, die unter verfügbar ist <you-firebase-app>/app/hello. Um sie zu entfernen, müssen Sie den Express-Teil tatsächlich ein wenig umschreiben (der Middleware-Teil für die Validierung bleibt gleich - er funktioniert sehr gut) gut und dank Kommentaren durchaus verständlich).

/**
 * 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);

Mein Umschreiben, um loszuwerden /app:

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

module.exports = {
  hello
}
Jean d'arme
quelle
0

Ich habe Probleme damit, eine ordnungsgemäße Firebase-Authentifizierung in der Golang GCP-Funktion zu erhalten. Da es dafür eigentlich kein Beispiel gibt, habe ich beschlossen, diese winzige Bibliothek zu erstellen: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Jetzt können Sie Benutzer einfach mit firebase-auth authentifizieren (was sich von gcp-authentifizierten Funktionen unterscheidet und vom identitätsbewussten Proxy nicht direkt unterstützt wird).

Hier ist ein Beispiel für die Verwendung des Dienstprogramms:

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
}

Denken Sie --allow-unauthenticateddaran, Ihre Funktion mit Flag bereitzustellen (da die Firebase-Authentifizierung innerhalb der Funktionsausführung erfolgt).

Hoffe das wird dir helfen, da es mir geholfen hat. Ich war aus Leistungsgründen entschlossen, Golang für Cloud-Funktionen zu verwenden - Jędrzej

jblew
quelle
0

In Firebase ist es nur eine Frage der architektonischen Gestaltung , um Ihren Code und Ihre Arbeit zu vereinfachen :

  1. Verwenden Sie für öffentlich zugängliche Websites / Inhalte HTTPS-Trigger mitExpress . Verwenden Sie zur Steuerung dieses Sicherheitsaspekts nur die Einschränkung derselben Website oder einer bestimmten WebsiteCORS . Dies Expressist sinnvoll , da es für SEO aufgrund seines serverseitigen Rendering-Inhalts nützlich ist.
  2. Verwenden Sie für Apps, die eine Benutzerauthentifizierung erfordern , HTTPS Callable Firebase-Funktionen und verwenden Sie dann den contextParameter, um alle Probleme zu sparen. Dies ist auch sinnvoll, da beispielsweise eine mit AngularJS erstellte Single Page App für SEO schlecht ist. Da es sich jedoch um eine passwortgeschützte App handelt, benötigen Sie auch nicht viel SEO. In Bezug auf das Templating verfügt AngularJS über ein integriertes Templating, sodass keine serverseitige Vorlage mit erforderlich ist Express. Dann sollten aufrufbare Firebase-Funktionen gut genug sein.

In Anbetracht des oben Gesagten, keine Probleme mehr und das Leben leichter machen.

Antonio Ooi
quelle