Como proteger o ponto de extremidade HTTP da função de nuvem do firebase para permitir apenas usuários autenticados do Firebase?


141

Com a nova função de nuvem do firebase, decidi mudar alguns dos meus pontos de extremidade HTTP para o firebase. Tudo funciona muito bem ... Mas eu tenho o seguinte problema. Eu tenho dois pontos de extremidade criados por gatilhos HTTP (Cloud Functions)

  1. Um ponto de extremidade da API para criar usuários e retorna o token personalizado gerado pelo Firebase Admin SDK.
  2. Um ponto de extremidade da API para buscar certos detalhes do usuário.

Enquanto o primeiro ponto de extremidade estiver bom, mas para o meu segundo ponto de extremidade, gostaria de protegê-lo apenas para usuários autenticados. ou seja, alguém que tenha o token que eu gerei anteriormente.

Como faço para resolver isso?

Eu sei que podemos obter os parâmetros Header na função cloud usando

request.get('x-myheader')

mas existe uma maneira de proteger o terminal, assim como proteger o banco de dados em tempo real?


como você conseguiu o costume de token gerado pelo Firebase administração SDK no primeiro API
Amine Harbaoui

2
@AmineHarbaoui Eu tive a mesma pergunta. Veja esta página: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Respostas:


137

Há um exemplo de código oficial para o que você está tentando fazer. O que ilustra é como configurar sua função HTTPS para exigir um cabeçalho de Autorização com o token que o cliente recebeu durante a autenticação. A função usa a biblioteca firebase-admin para verificar o token.

Além disso, você pode usar " funções de chamada " para facilitar muito esse clichê, se seu aplicativo puder usar as bibliotecas cliente do Firebase.


2
Este exemplo de código ainda é válido? Ainda é assim que você abordaria isso hoje?
quer

1
@GalBracha Ainda deve ser válido hoje (31 de outubro de 2017).
Doug Stevenson

@DougStevenson essas chamadas 'console.log' terão algum impacto 'perceptível' no desempenho?
Sanka Darshana

1
Como o uso de funções de chamada tornará o clichê mais fácil? Pelo que entendi, essas são apenas funções de servidor "não-REST", não entendo realmente como elas se relacionam aqui. Obrigado.
1252748 18/02

2
@ 1252748 Se você ler a documentação vinculada, ela ficará clara. Ele lida com a passagem e a validação do token de autenticação automaticamente, para que você não precise escrever esse código nos dois lados.
Doug Stevenson

121

Conforme mencionado por @Doug, você pode usar firebase-adminpara verificar um token. Eu configurei um exemplo 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));
  });
});

No exemplo acima, eu também habilitei o CORS, mas isso é opcional. Primeiro, você obtém o Authorizationcabeçalho e descobre o token.

Em seguida, você pode usar firebase-adminpara verificar esse token. Você receberá as informações decodificadas para esse usuário na resposta. Caso contrário, se o token não for válido, ocorrerá um erro.


13
Votado como simples, e não depende de expresso como o exemplo oficial.
DarkNeuron

5
Você pode explicar mais sobre os cors?
pete

@pete: cors está apenas resolvendo o compartilhamento de recursos entre origens. Você pode pesquisar no Google para saber mais sobre isso.
31517 Leng Hoàng

O @pete Cors permite que você atinja o ponto final do firebase-backend de diferentes URLs.
22617 Walter Monecke

6
@RezaRahmati Você pode usar o getIdToken()método nos documentos do firebase do lado do cliente (por exemplo, firebase.auth().currentUser.getIdToken().then(token => console.log(token)))
Will

18

Como também mencionado por @Doug, você pode usar as funções de chamada para excluir algum código padrão do seu cliente e servidor.

Função de chamada 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 };
});

Pode ser chamado diretamente do seu cliente da seguinte maneira:

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

2

Os métodos acima autenticam o usuário usando a lógica interna da função, portanto a função ainda deve ser invocada para fazer a verificação.

Esse é um método totalmente bom, mas por uma questão de abrangência, existe uma alternativa:

Você pode definir uma função como "privada" para que não possa ser chamada, exceto por usuários registrados (você decide as permissões). Nesse caso, solicitações não autenticadas são negadas fora do contexto da função e a função não é invocada.

Aqui estão referências a (a) Configuração de funções como pública / privada e, em seguida, (b) autenticação de usuários finais às suas funções .

Observe que os documentos acima são para o Google Cloud Platform e, de fato, isso funciona porque todo projeto Firebase também é um projeto GCP. Uma ressalva relacionada a esse método é que, por escrito, ele funciona apenas com autenticação baseada em conta do Google.


1

Há um bom exemplo oficial usando o Express - pode ser útil no futuro: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (colado abaixo, apenas com certeza)

Lembre-se de que exports.appdisponibiliza suas funções sob /appslug (nesse caso, existe apenas uma função e sob <you-firebase-app>/app/hello. Para se livrar dela, é necessário reescrever um pouco a parte do Express (a parte do middleware para validação permanece a mesma - funciona muito bom e é bastante compreensível graças aos comentários).

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

Minha reescrita para se livrar de /app:

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

module.exports = {
  hello
}

0

Eu tenho me esforçado para obter uma autenticação adequada da base de firmas na função golang GCP. Na verdade, não há exemplo para isso, então decidi criar esta pequena biblioteca: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Agora você pode autenticar facilmente os usuários usando o firebase-auth (que é diferente das funções gcp-authenticated) e não é diretamente suportado pelo proxy que reconhece a identidade).

Aqui está um exemplo de uso do utilitário:

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
}

Lembre-se de implantar sua função com --allow-unauthenticatedsinalizador (porque a autenticação do firebase ocorre dentro da execução da função).

Espero que isso ajude você, pois me ajudou. Eu estava determinado a usar golang para funções da nuvem por razões de desempenho - Jędrzej


0

No Firebase, para simplificar seu código e seu trabalho, é apenas uma questão de design de arquitetura :

  1. Para sites / conteúdos acessíveis ao público , use gatilhos HTTPS comExpress . Para restringir apenas o mesmo site ou site específico , use CORSpara controlar esse aspecto da segurança. Isso faz sentido porque Expressé útil para SEO devido ao seu conteúdo de renderização no servidor.
  2. Para aplicativos que exigem autenticação do usuário , use as Funções HTTPS Callable Firebase e use o contextparâmetro para salvar todos os aborrecimentos. Isso também faz sentido, porque, como um aplicativo de página única criado com o AngularJS - o AngularJS é ruim para o SEO, mas como é um aplicativo protegido por senha, você também não precisa muito do SEO. Quanto à modelagem, o AngularJS tem modelagem embutida, portanto, não há necessidade de modelo de corte com Express. As funções de chamada do Firebase devem ser boas o suficiente.

Com o exposto, não há mais problemas e facilita a vida.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.