¿Cómo proteger el punto final HTTP de Firebase Cloud Function para permitir solo usuarios autenticados de Firebase?

141

Con la nueva función de nube de Firebase, he decidido mover algunos de mis puntos finales HTTP a Firebase. Todo funciona muy bien ... Pero tengo el siguiente problema. Tengo dos puntos finales construidos por disparadores HTTP (Funciones en la nube)

  1. Un punto final de API para crear usuarios y devuelve el token personalizado generado por Firebase Admin SDK.
  2. Un punto final de API para obtener ciertos detalles del usuario.

Si bien el primer punto final está bien, pero para mi segundo punto final, me gustaría protegerlo solo para usuarios autenticados. es decir, alguien que tiene el token que generé anteriormente.

¿Cómo hago para resolver esto?

Sé que podemos obtener los parámetros de encabezado en la función de nube usando

request.get('x-myheader')

pero ¿hay alguna manera de proteger el punto final al igual que proteger la base de datos en tiempo real?

mono espacial
fuente
¿Cómo
obtuviste
2
@AmineHarbaoui Tenía la misma pregunta. Ver esta página: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Respuestas:

137

Hay una muestra de código oficial para lo que estás intentando hacer. Lo que ilustra es cómo configurar su función HTTPS para requerir un encabezado de autorización con el token que recibió el cliente durante la autenticación. La función usa la biblioteca firebase-admin para verificar el token.

Además, puede usar " funciones invocables " para facilitar mucho esta versión repetitiva, si su aplicación puede usar las bibliotecas de cliente de Firebase.

Doug Stevenson
fuente
2
¿Sigue siendo válido este ejemplo de código? ¿Sigue siendo así como abordarías esto hoy?
Gal Bracha
1
@GalBracha Todavía debería ser válido hoy (31 de octubre de 2017).
Doug Stevenson el
@DougStevenson ¿tendrán esas llamadas 'console.log' algún impacto 'notable' en el rendimiento?
Sanka Darshana
1
¿De qué manera el uso de funciones invocables facilitará la repetitiva? Por lo que entiendo, esas son solo funciones de servidor "no REST", realmente no entiendo cómo se relacionan aquí. Gracias.
1252748
2
@ 1252748 Si lee la documentación vinculada, quedará claro. Maneja el paso y la validación del token de autenticación automáticamente, por lo que no tiene que escribir ese código en ninguno de los lados.
Doug Stevenson
121

Como mencionó @Doug, puede usar firebase-adminpara verificar un token. He configurado un ejemplo rápido:

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

En el ejemplo anterior, también he habilitado CORS, pero eso es opcional. Primero, obtienes el Authorizationencabezado y descubres el token.

Luego, puede usar firebase-adminpara verificar ese token. Obtendrá la información decodificada para ese usuario en la respuesta. De lo contrario, si el token no es válido, arrojará un error.

Será
fuente
13
Votado como simple, y no depende de express como lo hace el ejemplo oficial.
DarkNeuron
55
¿Puedes explicar más sobre los cors?
pete
@pete: cors solo está resolviendo el intercambio de recursos de origen cruzado. Puedes buscar en Google para saber más al respecto.
Lạng Hoàng
@pete Cors te permite alcanzar ese punto final de back-end de firebase desde diferentes URL.
Walter Monecke
66
@RezaRahmati Puede usar el getIdToken()método en documentos del lado del cliente (por ejemplo, firebasefirebase.auth().currentUser.getIdToken().then(token => console.log(token)) )
Será el
18

Como también mencionó @Doug, puede usar las funciones invocables para excluir algunos códigos repetitivos de su cliente y su servidor.

Función invocable de examen:

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

Se puede invocar directamente desde su cliente así:

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

Los métodos anteriores autentican al usuario usando lógica dentro la función, por lo que aún se debe invocar la función para realizar la comprobación.

Es un método totalmente bueno, pero en aras de la comprensión, hay una alternativa:

Puede configurar una función como "privada" para que no pueda ser invocada excepto por usuarios registrados (usted decide los permisos). En este caso, las solicitudes no autenticadas se rechazan fuera del contexto de la función, y la función no se rechaza. invoca en absoluto.

Aquí hay referencias a (a) Configurar funciones como públicas / privadas , y luego (b) autenticar a los usuarios finales en sus funciones .

Tenga en cuenta que los documentos anteriores son para Google Cloud Platform, y de hecho, esto funciona porque cada proyecto de Firebase también es un proyecto GCP. Una advertencia relacionada con este método es que, al momento de escribir, solo funciona con la autenticación basada en la cuenta de Google.

Tedskovsky
fuente
1

Hay un buen ejemplo oficial sobre el uso de Express: puede ser útil en el futuro: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (pegado a continuación solo sin lugar a duda)

Tenga en cuenta que exports.appsus funciones están disponibles bajo /appslug (en este caso, solo hay una función y está disponible bajo <you-firebase-app>/app/hello. Para deshacerse de ella, en realidad necesita reescribir la parte Express un poco (la parte de middleware para la validación sigue siendo la misma, funciona muy bien) bueno y es bastante comprensible gracias a los comentarios).

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

Mi reescritura para deshacerme de /app:

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

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

He estado luchando para obtener la autenticación de Firebase adecuada en la función Golang GCP. En realidad no hay ningún ejemplo para eso, así que decidí construir esta pequeña biblioteca: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Ahora puede autenticar fácilmente a los usuarios usando firebase-auth (que es distinto de las funciones autenticadas de gcp y no es compatible directamente con el proxy de reconocimiento de identidad).

Aquí hay un ejemplo del uso de la utilidad:

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
}

Solo tenga en cuenta implementar su función con la --allow-unauthenticatedmarca (porque la autenticación de Firebase ocurre dentro de la ejecución de la función).

Espero que esto te ayude como me ayudó a mí. Estaba decidido a usar golang para funciones en la nube por razones de rendimiento - Jędrzej

jblew
fuente
0

En Firebase, para simplificar su código y su trabajo, es solo una cuestión de diseño arquitectónico :

  1. Para sitios / contenidos accesibles al público , use disparadores HTTPS conExpress . Para restringir solo un sitio samesite o específico , use CORSpara controlar este aspecto de la seguridad. Esto tiene sentido porqueExpress es útil para SEO debido a su contenido de representación del lado del servidor.
  2. Para las aplicaciones que requieren autenticación de usuario , use HTTPS Callable Firebase Functions , luego use el contextparámetro para guardar todas las molestias. Esto también tiene sentido, porque, como una aplicación de una sola página creada con AngularJS, AngularJS es mala para el SEO, pero como es una aplicación protegida por contraseña, tampoco necesita mucho SEO. En cuanto a la creación de plantillas, AngularJS tiene plantillas incorporadas, por lo que no es necesario usar una plantilla del lado del servidor Express. Entonces las funciones invocables de Firebase deberían ser lo suficientemente buenas.

Con lo anterior en mente, no más problemas y haz la vida más fácil.

Antonio Ooi
fuente