Bagaimana cara melindungi Firebase Fungsi Cloud HTTP endpoint untuk hanya mengizinkan pengguna terotentikasi Firebase?

141

Dengan fungsi cloud firebase baru saya telah memutuskan untuk memindahkan beberapa endpoint HTTP saya ke firebase. Semuanya berfungsi dengan baik ... Tetapi saya memiliki masalah berikut. Saya memiliki dua titik akhir yang dibangun oleh HTTP Triggers (Cloud Functions)

  1. Titik akhir API untuk membuat pengguna dan mengembalikan Token khusus yang dihasilkan oleh Firebase Admin SDK.
  2. Titik akhir API untuk mengambil detail pengguna tertentu.

Meskipun titik akhir pertama baik-baik saja, tetapi untuk titik akhir kedua saya ingin melindunginya hanya untuk pengguna yang diautentikasi. artinya seseorang yang memiliki token yang saya hasilkan sebelumnya.

Bagaimana saya menyelesaikan masalah ini?

Saya tahu kita bisa mendapatkan parameter Header di fungsi cloud menggunakan

request.get('x-myheader')

tetapi apakah ada cara untuk melindungi titik akhir seperti melindungi basis data waktu nyata?

spaceMonkey
sumber
bagaimana Anda mendapatkan token khusus yang dihasilkan oleh Firebase Admin SDK di API pertama
Amine Harbaoui
2
@AmineHarbaoui saya punya pertanyaan yang sama. Lihat halaman ini: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Jawaban:

137

Ada contoh kode resmi untuk apa yang Anda coba lakukan. Apa yang diilustrasikannya adalah bagaimana mengatur fungsi HTTPS Anda untuk meminta header Otorisasi dengan token yang diterima klien selama otentikasi. Fungsi ini menggunakan pustaka firebase-admin untuk memverifikasi token.

Selain itu, Anda dapat menggunakan " fungsi yang dapat dipanggil " untuk membuat banyak boilerplate ini lebih mudah, jika aplikasi Anda dapat menggunakan pustaka klien Firebase.

Doug Stevenson
sumber
2
Apakah sampel kode ini Masih valid? Apakah ini masih bagaimana Anda akan mengatasinya hari ini?
Gal Bracha
1
@GalBracha Seharusnya masih berlaku hari ini (31 Okt 2017).
Doug Stevenson
@DougStevenson akankah panggilan 'console.log' tersebut berdampak pada kinerja?
Sanka Darshana
1
Bagaimana menggunakan fungsi-fungsi yang dapat dipanggil membuat boilerplate lebih mudah? Dari apa yang saya pahami itu hanyalah fungsi server "non-REST", saya tidak begitu mengerti bagaimana mereka berhubungan di sini. Terima kasih.
1252748
2
@ 1252748 Jika Anda membaca dokumentasi yang ditautkan, itu akan menjadi jelas. Ini menangani kelulusan dan validasi token auth secara otomatis, jadi Anda tidak perlu menulis kode itu di kedua sisi.
Doug Stevenson
121

Seperti yang disebutkan oleh @Doug, Anda dapat menggunakan firebase-adminuntuk memverifikasi token. Saya telah menyiapkan contoh cepat:

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

Pada contoh di atas, saya juga telah mengaktifkan CORS, tetapi itu opsional. Pertama, Anda mendapatkan Authorizationtajuk dan mencari tahutoken .

Kemudian, Anda dapat menggunakan firebase-adminuntuk memverifikasi token itu. Anda akan mendapatkan informasi yang diterjemahkan untuk pengguna itu dalam respons. Kalau tidak, jika token tidak valid, itu akan menimbulkan kesalahan.

Akan
sumber
13
Terpilih karena sederhana, dan tidak bergantung pada ekspres seperti contoh resmi.
DarkNeuron
5
Bisakah Anda menjelaskan lebih lanjut tentang kors?
pete
@pete: cors hanya memecahkan berbagi sumber daya lintas-asal. Anda dapat google untuk mengetahui lebih banyak tentang itu.
Lạng Hoàng
@ lawan Cors memungkinkan Anda untuk mencapai titik akhir firebase-backend dari url yang berbeda.
Walter Monecke
6
@RezaRahmati Anda dapat menggunakan getIdToken()metode ini di sisi klien (mis. firebase.auth().currentUser.getIdToken().then(token => console.log(token))) Firebase docs
Will
18

Seperti juga disebutkan oleh @Doug, Anda dapat menggunakan Fungsi Callable untuk mengecualikan beberapa kode boilerplate dari klien Anda dan server Anda.

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

Itu dapat dipanggil langsung dari klien Anda seperti:

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

Metode di atas mengautentikasi pengguna menggunakan logika di dalamnya dalam fungsi, sehingga fungsi tersebut masih harus dipanggil untuk melakukan pemeriksaan.

Itu metode yang sangat bagus, tetapi demi kelengkapan, ada alternatif lain:

Anda dapat mengatur fungsi menjadi "pribadi" sehingga tidak dapat dipanggil kecuali oleh pengguna terdaftar (Anda memutuskan izin). Dalam kasus ini, permintaan yang tidak diautentikasi ditolak di luar konteks fungsi, dan fungsi tersebut tidak dipanggil sama sekali.

Berikut adalah referensi untuk (a) Mengonfigurasi fungsi sebagai publik / pribadi , dan kemudian (b) mengautentikasi pengguna akhir ke fungsi Anda .

Perhatikan bahwa dokumen di atas adalah untuk Google Cloud Platform, dan memang, ini berhasil karena setiap proyek Firebase juga merupakan proyek GCP. Peringatan terkait dengan metode ini adalah bahwa, pada saat penulisan, ini hanya berfungsi dengan otentikasi berbasis akun Google.

Tedskovsky
sumber
1

Ada contoh resmi yang bagus tentang hal itu menggunakan Express - mungkin berguna di masa depan: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (disisipkan di bawah ini hanya tentunya)

Ingatlah bahwa exports.appmembuat fungsi Anda tersedia di bawah /appsiput (dalam hal ini hanya ada satu fungsi dan tersedia di bawah <you-firebase-app>/app/hello. Untuk menyingkirkannya Anda sebenarnya perlu menulis ulang bagian Express sedikit (bagian middleware untuk validasi tetap sama - itu berfungsi sangat bagus dan cukup bisa dimengerti berkat komentar).

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

Penulisan ulang saya untuk menyingkirkan /app:

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

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

Saya telah berjuang untuk mendapatkan otentikasi firebase yang tepat dalam fungsi GCP golang. Sebenarnya tidak ada contoh untuk itu, jadi saya memutuskan untuk membangun perpustakaan kecil ini: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

Sekarang Anda dapat dengan mudah mengautentikasi pengguna menggunakan firebase-auth (yang berbeda dari fungsi-fungsi yang diautentikasi gcp dan tidak secara langsung didukung oleh proxy-aware-proxy).

Berikut adalah contoh penggunaan utilitas:

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
}

Hanya perlu diingat untuk menyebarkan fungsi Anda dengan --allow-unauthenticatedflag (karena otentikasi firebase terjadi di dalam eksekusi fungsi).

Semoga ini bisa membantu Anda karena itu membantu saya. Saya bertekad untuk menggunakan golang untuk fungsi cloud untuk alasan kinerja - Jędrzej

jblew
sumber
0

Di Firebase, untuk menyederhanakan kode dan pekerjaan Anda, itu hanya masalah desain arsitektur :

  1. Untuk situs / konten yang dapat diakses publik , gunakan pemicu HTTPS denganExpress . Untuk membatasi hanya samesite atau situs tertentu saja , gunakan CORSuntuk mengontrol aspek keamanan ini. Ini masuk akal karena Expressberguna untuk SEO karena konten rendering sisi servernya.
  2. Untuk aplikasi yang memerlukan otentikasi pengguna , gunakan HTTPS Callable Firebase Functions , lalu gunakan contextparameter untuk menyimpan semua kerepotan. Ini juga masuk akal, karena seperti Aplikasi Halaman Tunggal yang dibangun dengan AngularJS - AngularJS buruk untuk SEO, tetapi karena ini adalah aplikasi yang dilindungi kata sandi, Anda juga tidak memerlukan banyak SEO. Sedangkan untuk templating, AngularJS memiliki templating bawaan, jadi tidak perlu untuk template sisi-potong Express. Maka Firebase Callable Functions harus cukup baik.

Dengan pemikiran di atas, tidak ada lagi kerumitan dan membuat hidup lebih mudah.

Antonio Ooi
sumber