วิธีป้องกันจุดสิ้นสุด HTTP ของฟังก์ชัน Firebase Cloud เพื่ออนุญาตเฉพาะผู้ใช้ที่ตรวจสอบสิทธิ์ Firebase


152

ด้วยฟังก์ชันคลาวด์ firebase ใหม่ฉันได้ตัดสินใจย้ายปลายทาง HTTP บางส่วนไปยัง firebase ทุกอย่างใช้งานได้ดี ... แต่ฉันมีปัญหาต่อไปนี้ ฉันมีจุดสิ้นสุดสองจุดที่สร้างโดย HTTP Triggers (Cloud Functions)

  1. ปลายทาง API สำหรับสร้างผู้ใช้และส่งคืนโทเค็นที่กำหนดเองที่สร้างโดย Firebase Admin SDK
  2. ปลายทาง API เพื่อดึงรายละเอียดผู้ใช้บางอย่าง

แม้ว่าจุดสิ้นสุดแรกจะใช้ได้ดี แต่สำหรับจุดสิ้นสุดที่สองของฉันฉันต้องการปกป้องมันสำหรับผู้ใช้ที่ผ่านการพิสูจน์ตัวตนเท่านั้น หมายถึงคนที่มีโทเค็นที่ฉันสร้างไว้ก่อนหน้านี้

ฉันจะแก้ปัญหานี้ได้อย่างไร

ฉันรู้ว่าเราสามารถรับพารามิเตอร์ส่วนหัวในฟังก์ชันคลาวด์โดยใช้

request.get('x-myheader')

แต่มีวิธีป้องกันปลายทางเช่นเดียวกับการปกป้องฐานข้อมูลแบบเรียลไทม์หรือไม่


คุณได้รับโทเค็นที่กำหนดเองที่สร้างโดย Firebase Admin SDK ใน API แรกได้อย่างไร
Amine Harbaoui

2
@AmineHarbaoui ฉันมีคำถามเดียวกัน ดูหน้านี้: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

คำตอบ:


147

มีตัวอย่างโค้ดอย่างเป็นทางการสำหรับสิ่งที่คุณกำลังพยายามทำ สิ่งที่แสดงให้เห็นคือวิธีตั้งค่าฟังก์ชัน HTTPS ของคุณเพื่อต้องการส่วนหัวการอนุญาตพร้อมโทเค็นที่ไคลเอ็นต์ได้รับระหว่างการตรวจสอบสิทธิ์ ฟังก์ชันนี้ใช้ไลบรารี firebase-admin เพื่อตรวจสอบโทเค็น

นอกจากนี้คุณสามารถใช้ " ฟังก์ชันที่เรียกได้ " เพื่อทำให้ต้นแบบนี้ง่ายขึ้นจำนวนมากหากแอปของคุณสามารถใช้ไลบรารีไคลเอ็นต์ Firebase ได้


2
ตัวอย่างโค้ดนี้ยังใช้ได้หรือไม่ วันนี้คุณจะจัดการกับเรื่องนี้หรือไม่?
Gal Bracha

1
@GalBracha น่าจะยังใช้ได้วันนี้ (31 ต.ค. 2560)
Doug Stevenson

@DougStevenson การโทร 'console.log' เหล่านั้นจะมีผลกระทบที่ 'เห็นได้ชัดเจน' ต่อประสิทธิภาพหรือไม่?
Sanka Darshana

2
การใช้ฟังก์ชันที่เรียกได้จะทำให้ต้นแบบง่ายขึ้นได้อย่างไร จากสิ่งที่ฉันเข้าใจว่าสิ่งเหล่านี้เป็นเพียงฟังก์ชันเซิร์ฟเวอร์ "ไม่ใช่ REST" ฉันไม่เข้าใจจริงๆว่ามันเกี่ยวข้องกับที่นี่อย่างไร ขอบคุณ.
1252748

2
@ 1252748 ถ้าคุณอ่านเอกสารที่เชื่อมโยงมันจะชัดเจน จะจัดการการส่งผ่านและการตรวจสอบความถูกต้องของโทเค็นการตรวจสอบความถูกต้องโดยอัตโนมัติดังนั้นคุณจึงไม่จำเป็นต้องเขียนโค้ดนั้นในด้านใดด้านหนึ่ง
Doug Stevenson

127

ดังที่ @Doug กล่าวไว้คุณสามารถใช้firebase-adminเพื่อยืนยันโทเค็น ฉันได้สร้างตัวอย่างสั้น ๆ :

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

ในตัวอย่างด้านบนฉันได้เปิดใช้งาน CORS ด้วย แต่เป็นทางเลือก ขั้นแรกคุณจะได้รับAuthorizationส่วนหัวและค้นหาไฟล์token .

จากนั้นคุณสามารถใช้firebase-adminเพื่อตรวจสอบโทเค็นนั้น คุณจะได้รับข้อมูลที่ถอดรหัสสำหรับผู้ใช้รายนั้นในการตอบกลับ มิฉะนั้นหากโทเค็นไม่ถูกต้องจะทำให้เกิดข้อผิดพลาด


14
เพิ่มคะแนนเป็นเรื่องง่ายและไม่ได้ขึ้นอยู่กับการแสดงออกเหมือนตัวอย่างอย่างเป็นทางการ
DarkNeuron

5
คุณสามารถอธิบายเพิ่มเติมเกี่ยวกับ cors ได้หรือไม่?
pete

@pete: cors เป็นเพียงการแก้ปัญหาการแบ่งปันทรัพยากรข้ามแหล่งที่มา คุณสามารถ google เพื่อทราบข้อมูลเพิ่มเติม
LạngHoàng

@pete Cors ช่วยให้คุณเข้าถึงจุดสิ้นสุดของ firebase-backend จาก URL ที่แตกต่างกัน
Walter Monecke

7
@RezaRahmati คุณสามารถใช้getIdToken()วิธีนี้ในฝั่งไคลเอ็นต์ (เช่นfirebase.auth().currentUser.getIdToken().then(token => console.log(token))) เอกสาร firebase
จะ

20

ตามที่ @Doug กล่าวไว้คุณสามารถใช้ฟังก์ชันที่เรียกได้เพื่อแยกรหัสต้นแบบบางส่วนออกจากไคลเอนต์และเซิร์ฟเวอร์ของคุณ

ฟังก์ชันเรียก 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 };
});

สามารถเรียกใช้โดยตรงจากลูกค้าของคุณดังนี้:

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

4

วิธีการข้างต้นรับรองความถูกต้องของผู้ใช้โดยใช้ตรรกะภายในฟังก์ชันดังนั้นจึงต้องเรียกใช้ฟังก์ชันดังกล่าวเพื่อทำการตรวจสอบ

นั่นเป็นวิธีการที่ดีโดยสิ้นเชิง แต่เพื่อความเข้าใจมีทางเลือกอื่น:

คุณสามารถตั้งค่าฟังก์ชันให้เป็น "ส่วนตัว" เพื่อให้ไม่สามารถเรียกใช้งานได้ยกเว้นโดยผู้ใช้ที่ลงทะเบียน (คุณเป็นผู้ตัดสินใจเกี่ยวกับสิทธิ์) ในกรณีนี้คำร้องขอที่ไม่ได้พิสูจน์ตัวตนจะถูกปฏิเสธนอกบริบทของฟังก์ชันและฟังก์ชันไม่ได้ถูกเรียกใช้เลย

ต่อไปนี้คือการอ้างอิงถึง (a) การกำหนดค่าฟังก์ชันเป็นสาธารณะ / ส่วนตัวจากนั้น (b) การพิสูจน์ตัวตนผู้ใช้ปลายทางกับฟังก์ชันของคุณการตรวจสอบผู้ใช้สามารถฟังก์ชั่นของคุณ

โปรดทราบว่าเอกสารดังกล่าวสำหรับแพลตฟอร์ม Google Cloud และแน่นอนงานนี้เพราะทุกโครงการ Firebase เป็นยังโครงการ GCP ข้อแม้ที่เกี่ยวข้องกับวิธีนี้คือในขณะที่เขียนจะใช้ได้กับการตรวจสอบความถูกต้องโดยใช้บัญชี Google เท่านั้น


0

มีตัวอย่างอย่างเป็นทางการที่ดีในการใช้ Express - อาจมีประโยชน์ในอนาคต: https://github.com/firebase/functions-samples/blob/master/authorized-https-endpoint/functions/index.js (วางด้านล่างเพียง แน่นอน)

โปรดทราบว่าexports.appทำให้ฟังก์ชันของคุณพร้อมใช้งานภายใต้/appslug (ในกรณีนี้มีเพียงฟังก์ชันเดียวและพร้อมใช้งานภายใต้<you-firebase-app>/app/helloหากต้องการกำจัดคุณต้องเขียนส่วน Express ใหม่เล็กน้อย (ส่วนมิดเดิลแวร์สำหรับการตรวจสอบความถูกต้องยังคงเหมือนเดิม - ใช้งานได้ดี ดีและค่อนข้างเข้าใจขอบคุณความคิดเห็น)

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

เขียนซ้ำของฉันเพื่อกำจัด/app:

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

module.exports = {
  hello
}

0

ฉันพยายามดิ้นรนเพื่อให้ได้รับการตรวจสอบสิทธิ์ firebase ที่เหมาะสมในฟังก์ชัน golang GCP ไม่มีตัวอย่างจริงๆดังนั้นฉันจึงตัดสินใจสร้างห้องสมุดเล็ก ๆ นี้: https://github.com/Jblew/go-firebase-auth-in-gcp-functions

ตอนนี้คุณสามารถตรวจสอบสิทธิ์ผู้ใช้โดยใช้ firebase-auth ได้อย่างง่ายดาย (ซึ่งแตกต่างจากฟังก์ชันที่พิสูจน์ตัวตน gcp และไม่ได้รับการสนับสนุนโดยตรงจาก identity-awareness-proxy)

นี่คือตัวอย่างของการใช้ยูทิลิตี้:

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
}

โปรดทราบว่าคุณต้องปรับใช้ฟังก์ชันกับ--allow-unauthenticatedแฟล็ก (เนื่องจากการตรวจสอบสิทธิ์ของ Firebase เกิดขึ้นภายในการเรียกใช้ฟังก์ชัน)

หวังว่านี่จะช่วยคุณได้ตามที่ช่วยฉัน ฉันตั้งใจจะใช้ golang สำหรับฟังก์ชันระบบคลาวด์ด้วยเหตุผลด้านประสิทธิภาพ - Jędrzej


0

ใน Firebase เพื่อลดความซับซ้อนของโค้ดและงานของคุณมันเป็นเพียงเรื่องของการออกแบบสถาปัตยกรรม :

  1. สำหรับเว็บไซต์ที่สามารถเข้าถึงประชาชน / เนื้อหาใช้HTTPS Expressทริกเกอร์ด้วย หากต้องการ จำกัด เฉพาะ samesite หรือเฉพาะไซต์เท่านั้นให้ใช้CORSเพื่อควบคุมด้านความปลอดภัยนี้ สิ่งนี้สมเหตุสมผลเพราะExpressมีประโยชน์สำหรับ SEO เนื่องจากเนื้อหาการแสดงผลฝั่งเซิร์ฟเวอร์
  2. สำหรับแอปที่ต้องการการตรวจสอบผู้ใช้ให้ใช้ฟังก์ชัน HTTPS Callable Firebaseจากนั้นใช้contextพารามิเตอร์เพื่อบันทึกความยุ่งยากทั้งหมด สิ่งนี้ก็สมเหตุสมผลเช่นกันเนื่องจากแอปหน้าเดียวที่สร้างด้วย AngularJS - AngularJS นั้นไม่ดีสำหรับ SEO แต่เนื่องจากเป็นแอปที่มีการป้องกันด้วยรหัสผ่านคุณจึงไม่จำเป็นต้องใช้ SEO มากนัก สำหรับเทมเพลต AngularJS มีเทมเพลตในตัวดังนั้นจึงไม่จำเป็นต้องใช้เทมเพลตฝั่งตัดกับExpressไฟล์. ดังนั้น Firebase Callable Functions ก็น่าจะดีพอ

เมื่อคำนึงถึงสิ่งที่กล่าวมาข้างต้นก็ไม่ยุ่งยากและทำให้ชีวิตง่ายขึ้นอีกต่อไป

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.