Jak chronić punkt końcowy HTTP Firebase Cloud Function, aby zezwolić tylko na uwierzytelnionych użytkowników Firebase?

152

Dzięki nowej funkcji chmury Firebase postanowiłem przenieść część mojego punktu końcowego HTTP do Firebase. Wszystko działa świetnie ... Ale mam następujący problem. Mam dwa punkty końcowe zbudowane przez wyzwalacze HTTP (funkcje w chmurze)

  1. Punkt końcowy interfejsu API służący do tworzenia użytkowników i zwracania niestandardowego tokenu wygenerowanego przez pakiet Firebase Admin SDK.
  2. Punkt końcowy interfejsu API do pobierania określonych danych użytkownika.

Chociaż pierwszy punkt końcowy jest w porządku, ale dla mojego drugiego punktu końcowego chciałbym go chronić tylko dla uwierzytelnionych użytkowników. czyli ktoś, kto ma wygenerowany przeze mnie token.

Jak mam rozwiązać ten problem?

Wiem, że możemy uzyskać parametry nagłówka w funkcji chmury za pomocą

request.get('x-myheader')

ale czy istnieje sposób ochrony punktu końcowego tak samo, jak ochrona bazy danych czasu rzeczywistego?

spaceMonkey
źródło
jak otrzymałeś niestandardowy token wygenerowany przez Firebase Admin SDK w pierwszym API
Amine Harbaoui
2
@AmineHarbaoui Miałem to samo pytanie. Zobacz tę stronę: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Odpowiedzi:

147

Istnieje oficjalny przykład kodu dla tego, co próbujesz zrobić. Pokazuje, jak skonfigurować funkcję HTTPS tak, aby wymagała nagłówka Authorization z tokenem, który klient otrzymał podczas uwierzytelniania. Funkcja korzysta z biblioteki firebase-admin do weryfikacji tokenu.

Możesz też użyć „ funkcji wywoływalnych ”, aby ułatwić sobie wiele z tego standardowego schematu, jeśli Twoja aplikacja może korzystać z bibliotek klienta Firebase.

Doug Stevenson
źródło
2
Czy ten przykładowy kod jest nadal ważny? Czy nadal w ten sposób można to dziś rozwiązać?
Gal Bracha
1
@GalBracha Powinien nadal obowiązywać dzisiaj (31.10.2017).
Doug Stevenson
@DougStevenson Czy wywołania „console.log” będą miały „zauważalny” wpływ na wydajność?
Sanka Darshana
2
W jaki sposób użycie funkcji wywoływalnych ułatwi tworzenie schematu? Z tego, co rozumiem, są to tylko funkcje serwera „inne niż REST”, nie bardzo rozumiem, jak one się tutaj odnoszą. Dzięki.
1252748
2
@ 1252748 Jeśli przeczytasz dołączoną dokumentację, stanie się jasne. Automatycznie obsługuje przekazywanie i sprawdzanie poprawności tokenu uwierzytelniania, więc nie musisz pisać tego kodu po żadnej stronie.
Doug Stevenson
127

Jak wspomniał @Doug, możesz użyć firebase-admindo weryfikacji tokena. Ustawiłem szybki przykład:

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

W powyższym przykładzie włączyłem również CORS, ale jest to opcjonalne. Najpierw zdobądź Authorizationnagłówek i dowiesz się, że token.

Następnie możesz użyć firebase-admindo zweryfikowania tego tokena. W odpowiedzi otrzymasz zdekodowane informacje dla tego użytkownika. W przeciwnym razie, jeśli token jest nieprawidłowy, zgłosi błąd.

Będzie
źródło
14
Głosowano za, ponieważ jest to proste i nie zależy od ekspresu, jak robi to oficjalny przykład.
DarkNeuron
5
Czy możesz wyjaśnić więcej o korsach?
pete,
@pete: cors rozwiązuje tylko problem udostępniania zasobów między źródłami. Możesz znaleźć w Google, aby dowiedzieć się więcej na ten temat.
Lạng Hoàng
@Pete Cors pozwala na trafienie do tego punktu końcowego zaplecza Firebase z różnych adresów URL.
Walter Monecke,
7
@RezaRahmati Możesz użyć tej getIdToken()metody po stronie klienta (np. firebase.auth().currentUser.getIdToken().then(token => console.log(token))) W dokumentach firebase
Will
20

Jak również wspomniał @Doug, możesz użyć funkcji Callable Functions , aby wykluczyć jakiś standardowy kod z klienta i serwera.

Przykładowa funkcja wywoływana:

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

Można go wywołać bezpośrednio z klienta, na przykład:

firebase.functions().httpsCallable('getData')({query}).then(result => console.log(result));
Benny
źródło
4

Powyższe metody uwierzytelniają użytkownika przy użyciu logiki wewnątrz funkcji, więc funkcja musi być nadal wywoływana w celu sprawdzenia.

To całkiem dobra metoda, ale ze względu na kompleksowość istnieje alternatywa:

Możesz ustawić funkcję jako „prywatną”, aby nie mogła być wywoływana z wyjątkiem zarejestrowanych użytkowników (Ty decydujesz o uprawnieniach). W takim przypadku nieuwierzytelnione żądania są odrzucane poza kontekstem funkcji, a funkcja nie jest w ogóle wywoływana.

Oto odniesienia do (a) Konfigurowania funkcji jako publicznych / prywatnych , a następnie (b) uwierzytelniania użytkowników końcowych w Twoich funkcjach .

Pamiętaj, że powyższe dokumenty dotyczą Google Cloud Platform i rzeczywiście to działa, ponieważ każdy projekt Firebase jest również projektem GCP. Powiązanym zastrzeżeniem z tą metodą jest to, że w chwili pisania działa ona tylko z uwierzytelnianiem opartym na koncie Google.

Tedskovsky
źródło
0

Jest na to fajny oficjalny przykład użycia Express - może się przydać w przyszłości: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (wklejam poniżej tylko na pewno)

Pamiętaj, exports.appże twoje funkcje są dostępne pod /appslugiem (w tym przypadku jest tylko jedna funkcja i jest dostępna pod <you-firebase-app>/app/hello. Aby się jej pozbyć, musisz trochę przepisać część Express (część oprogramowania pośredniego do walidacji pozostaje taka sama - działa bardzo dobre i dzięki komentarzom jest całkiem zrozumiałe).

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

Mój przepis, aby pozbyć się /app:

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

module.exports = {
  hello
}
jean d'arme
źródło
0

Starałem się uzyskać prawidłowe uwierzytelnienie Firebase w funkcji golang GCP. Właściwie nie ma na to przykładu, więc postanowiłem zbudować tę malutką bibliotekę: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Teraz możesz łatwo uwierzytelniać użytkowników za pomocą firebase-auth (który różni się od funkcji uwierzytelnionych przez gcp i nie jest bezpośrednio obsługiwany przez proxy rozpoznające tożsamość).

Oto przykład użycia narzędzia:

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
}

Po prostu pamiętaj, aby wdrożyć funkcję z --allow-unauthenticatedflagą (ponieważ uwierzytelnianie Firebase odbywa się wewnątrz wykonywania funkcji).

Mam nadzieję, że to ci pomoże, tak jak mi pomogło. Byłem zdecydowany używać golanga do funkcji chmury ze względów wydajnościowych - Jędrzej

jblew
źródło
0

W Firebase, aby uprościć kod i pracę, wystarczy zaprojektować architekturę :

  1. W przypadku publicznie dostępnych witryn / treści użyj wyzwalaczy HTTPS zExpress . Aby ograniczyć tylko tę samą lub określoną witrynę , użyj CORSdo kontrolowania tego aspektu bezpieczeństwa. Ma to sens, ponieważ Expressjest przydatne dla SEO ze względu na treść renderowaną po stronie serwera.
  2. W przypadku aplikacji, które wymagają uwierzytelniania użytkownika , użyj funkcji Firebase z możliwością contextwywołania HTTPS , a następnie użyj parametru, aby zapisać wszystkie kłopoty. Ma to również sens, ponieważ na przykład aplikacja jednostronicowa zbudowana za pomocą AngularJS - AngularJS jest szkodliwa dla SEO, ale ponieważ jest to aplikacja chroniona hasłem, nie potrzebujesz też dużo SEO. Jeśli chodzi o tworzenie szablonów, AngularJS ma wbudowane szablony, więc nie ma potrzeby stosowania szablonu po stronie serwera z Express. Wtedy funkcje wywoływalne Firebase powinny być wystarczająco dobre.

Mając to na uwadze, koniec z kłopotami i ułatwienie życia.

Antonio Ooi
źródło