205 votes

Comment protéger le point de terminaison HTTP de Firebase Cloud Function pour n'autoriser que les utilisateurs authentifiés par Firebase ?

Avec la nouvelle fonction cloud de firebase, j'ai décidé de déplacer certains de mes endpoints HTTP vers firebase. Tout fonctionne très bien... Mais j'ai le problème suivant. J'ai deux points d'extrémité construits par des déclencheurs HTTP (fonctions de nuage)

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

Le premier point d'accès est parfait, mais pour mon deuxième point d'accès, je voudrais le protéger uniquement pour les utilisateurs authentifiés, c'est-à-dire ceux qui possèdent le jeton que j'ai généré précédemment.

Comment dois-je m'y prendre pour résoudre ce problème ?

Je sais que nous pouvons obtenir les paramètres de l'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 comme on protège la base de données en temps réel ?

0 votes

Comment avez-vous obtenu le jeton personnalisé généré par Firebase Admin SDK dans la première API ?

2 votes

@AmineHarbaoui J'avais la même question. Voir cette page : firebase.google.com/docs/auth/admin/verify-id-tokens

3voto

Steve Klock Points 11

Il y a beaucoup d'informations importantes ici qui m'ont vraiment aidé, mais j'ai pensé qu'il serait bon de décomposer un exemple de travail simple pour toute personne utilisant Angular et essayant ceci pour la première fois. La documentation de Google Firebase est disponible à l'adresse suivante 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<any> {

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

1voto

jean d'arme Points 1100

Il existe un bel exemple officiel de l'utilisation d'Express, qui pourrait s'avérer utile à l'avenir : https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (collé ci-dessous juste pour être sûr)

Gardez à l'esprit que exports.app rend vos fonctions disponibles sous /app slug (dans ce cas, il n'y a qu'une seule fonction et elle est disponible sous la rubrique <you-firebase-app>/app/hello . Pour s'en débarrasser, il faut en fait réécrire un peu la partie Express (la partie middleware pour la validation reste la même - elle fonctionne très bien et est assez 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 se débarrasser de /app :

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

module.exports = {
  hello
}

1voto

jblew Points 78

Je me suis battu pour obtenir une authentification correcte de Firebase dans la fonction GCP de Golang. Il n'y a en fait aucun exemple pour cela, alors j'ai décidé de construire cette petite bibliothèque : https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Maintenant, vous pouvez facilement authentifier les utilisateurs en utilisant firebase-auth (qui est distinct de gcp-authenticated-functions et n'est pas directement pris en charge par l'identity-aware-proxy).

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
}

Gardez juste à l'esprit de déployer votre fonction avec --allow-unauthenticated (parce que l'authentification de firebase se produit à l'intérieur de l'exécution de la fonction).

J'espère que cela vous aidera comme cela m'a aidé. J'étais déterminé à utiliser golang pour les fonctions en nuage pour des raisons de performance. - Jedrzej

1voto

Ali Shoman Points 3

Vous pouvez prendre cela comme une fonction qui renvoie un booléen. Si l'utilisateur a vérifié ou non, vous continuerez ou arrêterez votre API. En outre, vous pouvez retourner les réclamations ou le résultat de l'utilisateur à partir de la variable decode.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X