Comment protéger le point de terminaison HTTP Firebase Cloud Function pour autoriser uniquement les utilisateurs authentifiés Firebase?

141

Avec la nouvelle fonction cloud de Firebase, j'ai décidé de déplacer une partie de mon point de terminaison HTTP vers Firebase. Tout fonctionne très bien ... Mais j'ai le problème suivant. J'ai deux points de terminaison créés par des déclencheurs HTTP (Cloud Functions)

  1. Un point de terminaison d'API pour créer des utilisateurs et renvoyer le jeton personnalisé généré par le SDK Firebase Admin.
  2. Un point de terminaison d'API pour récupérer certains détails utilisateur.

Bien que le premier point de terminaison convienne, mais pour mon deuxième point de terminaison, je voudrais le protéger uniquement pour les utilisateurs authentifiés. signifiant quelqu'un qui a le jeton que j'ai généré plus tôt.

Comment résoudre ce problème?

Je sais que nous pouvons obtenir les paramètres d'en-tête dans la fonction cloud en utilisant

request.get('x-myheader')

mais existe-t-il un moyen de protéger le point de terminaison tout comme la protection de la base de données en temps réel?

espaceMonkey
la source
comment avez-vous obtenu le jeton personnalisé généré par Firebase Admin SDK dans la première API
Amine Harbaoui
2
@AmineHarbaoui J'avais la même question. Voir cette page: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Réponses:

137

Il existe un exemple de code officiel pour ce que vous essayez de faire. Ce qu'il illustre est comment configurer votre fonction HTTPS pour exiger un en-tête d'autorisation avec le jeton que le client a reçu lors de l'authentification. La fonction utilise la bibliothèque firebase-admin pour vérifier le jeton.

En outre, vous pouvez utiliser des « fonctions appelables » pour simplifier une grande partie de ce passe-partout, si votre application est capable d'utiliser les bibliothèques clientes Firebase.

Doug Stevenson
la source
2
Cet exemple de code est-il toujours valide? Est-ce toujours ainsi que vous aborderiez ce problème aujourd'hui?
Gal Bracha
1
@GalBracha Il devrait encore être valide aujourd'hui (31 octobre 2017).
Doug Stevenson
@DougStevenson Ces appels «console.log» auront-ils un impact «notable» sur les performances?
Sanka Darshana
1
Comment l'utilisation des fonctions appelables facilitera-t-elle le passe-partout? D'après ce que je comprends, ce ne sont que des fonctions de serveur "non-REST", je ne comprends pas vraiment comment elles se rapportent ici. Merci.
1252748
2
@ 1252748 Si vous lisez la documentation liée, elle deviendra claire. Il gère automatiquement le passage et la validation du jeton d'authentification, vous n'avez donc pas à écrire ce code de chaque côté.
Doug Stevenson le
121

Comme mentionné par @Doug, vous pouvez utiliser firebase-adminpour vérifier un jeton. J'ai mis en place un exemple rapide:

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

Dans l'exemple ci-dessus, j'ai également activé CORS, mais c'est facultatif. Tout d'abord, vous obtenez l'en- Authorizationtête et découvrez le fichier token.

Ensuite, vous pouvez utiliser firebase-adminpour vérifier ce jeton. Vous obtiendrez les informations décodées pour cet utilisateur dans la réponse. Sinon, si le jeton n'est pas valide, il générera une erreur.

Volonté
la source
13
Évalué car il est simple et ne dépend pas d'express comme le fait l'exemple officiel.
DarkNeuron
5
Pouvez-vous expliquer plus sur les cors?
pete
@pete: cors résout simplement le partage de ressources inter-origines. Vous pouvez google pour en savoir plus.
Lạng Hoàng
@pete Cors vous permet d'atteindre ce point de terminaison firebase-backend à partir de différentes URL.
Walter Monecke
6
@RezaRahmati Vous pouvez utiliser la getIdToken()méthode de côté client (par exemple firebase.auth().currentUser.getIdToken().then(token => console.log(token))) Firebase docs
Will
18

Comme mentionné également par @Doug, vous pouvez utiliser des fonctions appelables afin d' exclure du code standard de votre client et de votre serveur.

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

Il peut être appelé directement depuis votre client comme ceci:

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

Les méthodes ci-dessus authentifient l'utilisateur à l'aide de la logique à l' intérieur de la fonction, de sorte que la fonction doit toujours être appelée pour effectuer la vérification.

C'est une méthode tout à fait fine, mais par souci d'exhaustivité, il existe une alternative:

Vous pouvez définir une fonction pour qu'elle soit "privée" afin qu'elle ne puisse être invoquée que par des utilisateurs enregistrés (vous décidez des autorisations). Dans ce cas, les demandes non authentifiées sont refusées en dehors du contexte de la fonction et la fonction n'est pas du tout appelée.

Voici des références à (a) la configuration des fonctions en tant que public / privé , puis (b) l' authentification des utilisateurs finaux auprès de vos fonctions .

Notez que les documents ci-dessus sont pour Google Cloud Platform, et en effet, cela fonctionne car chaque projet Firebase est également un projet GCP. Une mise en garde liée à cette méthode est que, au moment de la rédaction, elle ne fonctionne qu'avec l'authentification basée sur un compte Google.

Tedskovsky
la source
1

Il y a un bel exemple officiel à ce sujet en utilisant Express - peut être utile à l'avenir: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (collé ci-dessous juste pour sûr)

Gardez à l'esprit que exports.appvos fonctions sont disponibles sous /appslug (dans ce cas, il n'y a qu'une seule fonction et est disponible sous <you-firebase-app>/app/hello. Pour vous en débarrasser, vous devez en fait réécrire un peu la partie Express (la partie middleware pour la validation reste la même - cela fonctionne très bon et est tout à fait compréhensible grâce aux commentaires).

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

Ma réécriture pour me débarrasser de /app:

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

module.exports = {
  hello
}
jean d'arme
la source
0

J'ai eu du mal à obtenir une authentification Firebase appropriée dans la fonction golang GCP. Il n'y a en fait aucun exemple pour cela, j'ai donc décidé de créer cette petite bibliothèque: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Vous pouvez désormais authentifier facilement les utilisateurs à l'aide de firebase-auth (qui est distinct des fonctions gcp-authenticated-functions et n'est pas directement pris en charge par le proxy-aware-identity).

Voici un exemple d'utilisation de l'utilitaire:

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
}

N'oubliez pas de déployer votre fonction avec un --allow-unauthenticatedindicateur (car l'authentification Firebase se produit dans l'exécution de la fonction).

J'espère que cela vous aidera car cela m'a aidé. J'étais déterminé à utiliser golang pour les fonctions cloud pour des raisons de performances - Jędrzej

jblew
la source
0

Dans Firebase, afin de simplifier votre code et votre travail, ce n'est qu'une question de conception architecturale :

  1. Pour les sites / contenus accessibles au public , utilisez des déclencheurs HTTPS avecExpress . Pour limiter uniquement le même site ou un site spécifique , utilisez CORSpour contrôler cet aspect de la sécurité. Cela a du sens car il Expressest utile pour le référencement en raison de son contenu de rendu côté serveur.
  2. Pour les applications nécessitant une authentification utilisateur , utilisez les fonctions Firebase appelables HTTPS , puis utilisez le contextparamètre pour éviter tous les tracas. Cela a également du sens, car comme une application à page unique construite avec AngularJS - AngularJS est mauvais pour le référencement, mais comme il s'agit d'une application protégée par mot de passe, vous n'avez pas besoin non plus de beaucoup de référencement. En ce qui concerne les modèles, AngularJS a des modèles intégrés, donc pas besoin de modèle côté serveur avec Express. Les fonctions appelables Firebase devraient alors être assez bonnes.

Avec ce qui précède à l'esprit, plus de tracas et simplifiez-vous la vie.

Antonio Ooi
la source