Come proteggere l'endpoint HTTP della funzione cloud Firebase per consentire solo agli utenti autenticati Firebase?


141

Con la nuova funzione cloud Firebase ho deciso di spostare alcuni dei miei endpoint HTTP su Firebase. Tutto funziona alla grande ... Ma ho il seguente problema. Ho due endpoint creati da trigger HTTP (funzioni cloud)

  1. Un endpoint API per creare utenti e restituire il token personalizzato generato da Firebase Admin SDK.
  2. Un endpoint API per recuperare determinati dettagli utente.

Mentre il primo endpoint va bene, ma per il mio secondo end point vorrei proteggerlo solo per utenti autenticati. vale a dire qualcuno che ha il token che ho generato in precedenza.

Come posso risolvere questo?

So che possiamo ottenere i parametri Header nella funzione cloud usando

request.get('x-myheader')

ma c'è un modo per proteggere l'endpoint proprio come proteggere la base di dati in tempo reale?


come hai ottenuto il token personalizzato generato da Firebase Admin SDK nella prima API
Amine Harbaoui,

2
@AmineHarbaoui Ho avuto la stessa domanda. Vedi questa pagina: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Risposte:


137

C'è un esempio di codice ufficiale per quello che stai cercando di fare. Ciò che illustra è come impostare la funzione HTTPS per richiedere un'intestazione di autorizzazione con il token ricevuto dal client durante l'autenticazione. La funzione utilizza la libreria firebase-admin per verificare il token.

Inoltre, puoi utilizzare le " funzioni richiamabili " per semplificare molto questa piastra della caldaia, se la tua app è in grado di utilizzare le librerie client Firebase.


2
Questo esempio di codice è ancora valido? È ancora così che affronteresti questo oggi?
Gal Bracha,

1
@GalBracha Dovrebbe essere ancora valido oggi (31 ottobre 2017).
Doug Stevenson,

@DougStevenson queste chiamate 'console.log' avranno un impatto 'evidente' sulle prestazioni?
Sanka Darshana,

1
In che modo l'uso delle funzioni richiamabili renderà più semplice la caldaia? Da quello che capisco sono solo funzioni del server "non REST", non capisco davvero come si relazionano qui. Grazie.
1252748,

2
@ 1252748 Se leggi la documentazione collegata, diventerà chiara. Gestisce il passaggio e la convalida del token di autenticazione automaticamente, quindi non è necessario scrivere quel codice su entrambi i lati.
Doug Stevenson,

121

Come indicato da @Doug, è possibile utilizzare firebase-adminper verificare un token. Ho creato un rapido esempio:

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

Nell'esempio sopra, ho anche abilitato CORS, ma questo è facoltativo. Innanzitutto, ottieni l' Authorizationintestazione e scopri il token.

Quindi, è possibile utilizzare firebase-adminper verificare quel token. Otterrai le informazioni decodificate per quell'utente nella risposta. Altrimenti, se il token non è valido, genererà un errore.


13
Eseguito l'upgrade in quanto è semplice e non dipende da express come nell'esempio ufficiale.
DarkNeuron,

5
Puoi spiegare di più sul cors?
pete,

@pete: cors sta solo risolvendo la condivisione delle risorse tra le origini. Puoi google per saperne di più.
Lạng Hoàng,

@pete Cors ti consente di raggiungere l'end-point back-fire-base da diversi URL.
Walter Monecke,

6
@RezaRahmati Puoi utilizzare il getIdToken()metodo sui documenti lato client (ad es. firebase.auth().currentUser.getIdToken().then(token => console.log(token))) Firebase
Will,

18

Come accennato anche da @Doug, è possibile utilizzare le funzioni di chiamata per escludere un codice di tipo boilerplate dal client e dal server.

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

Può essere invocato direttamente dal tuo client in questo modo:

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

2

I metodi sopra indicati autenticano l'utente usando la logica all'interno della funzione, quindi la funzione deve essere ancora invocata per eseguire il controllo.

Questo è un metodo assolutamente eccellente, ma per motivi di comprensibilità, c'è un'alternativa:

È possibile impostare una funzione come "privata" in modo che non possa essere invocata se non dagli utenti registrati (si decide sulle autorizzazioni). In questo caso, le richieste non autenticate vengono negate al di fuori del contesto della funzione e la funzione non viene affatto invocata.

Qui ci sono riferimenti a (a) Configurare le funzioni come pubbliche / private , e quindi (b) autenticare gli utenti finali per le tue funzioni .

Tieni presente che i documenti di cui sopra sono per Google Cloud Platform e, in effetti, questo funziona perché ogni progetto Firebase è anche un progetto GCP. Un avvertimento correlato con questo metodo è che, al momento della scrittura, funziona solo con l'autenticazione basata su account Google.


1

C'è un bell'esempio ufficiale su di esso usando Express - potrebbe essere utile in futuro: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (incollato sotto solo di sicuro)

Tieni presente che exports.apprende disponibili le tue funzioni sotto /appslug (in questo caso c'è solo una funzione ed è disponibile sotto <you-firebase-app>/app/hello. Per sbarazzartene devi effettivamente riscrivere un po 'la parte Express (la parte middleware per la validazione rimane la stessa - funziona molto buono ed è abbastanza comprensibile grazie ai commenti).

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

La mia riscrittura per sbarazzarsi di /app:

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

module.exports = {
  hello
}

0

Ho avuto difficoltà a ottenere la corretta autenticazione firebase nella funzione GCP golang. In realtà non c'è alcun esempio per questo, quindi ho deciso di creare questa piccola libreria: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Ora puoi facilmente autenticare gli utenti usando firebase-auth (che è distinto dalle funzioni gcp-authenticated e non è direttamente supportato dal proxy di riconoscimento dell'identità).

Ecco un esempio dell'uso dell'utilità:

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
}

Ricorda solo di distribuire la tua funzione con --allow-unauthenticatedflag (poiché l'autenticazione Firebase si verifica all'interno dell'esecuzione della funzione).

Spero che questo ti aiuti come mi ha aiutato. Ero determinato a utilizzare il Golang per le funzioni cloud per motivi di prestazioni - Jędrzej


0

In Firebase, al fine di semplificare il tuo codice e il tuo lavoro, è solo una questione di progettazione architettonica :

  1. Per siti / contenuti accessibili al pubblico , utilizzare i trigger HTTPS conExpress . Per limitare solo lo stesso sito specifico o specifico , utilizzare CORSper controllare questo aspetto della sicurezza. Questo ha senso perché Expressè utile per SEO grazie al suo contenuto di rendering sul lato server.
  2. Per le app che richiedono l'autenticazione dell'utente , utilizzare le funzioni HTTPS Callable Firebase , quindi utilizzare il contextparametro per salvare tutti i problemi. Questo ha anche senso, perché come un'app a pagina singola costruita con AngularJS - AngularJS è dannosa per il SEO, ma poiché è un'app protetta da password, non è nemmeno necessario molto del SEO. Per quanto riguarda il modello, AngularJS ha il modello incorporato, quindi non è necessario un modello lato server con Express. Quindi le funzioni richiamabili di Firebase dovrebbero essere abbastanza buone.

Tenendo presente quanto sopra, non dovrai più preoccuparti e rendere la vita più semplice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.