Làm cách nào để bảo vệ điểm cuối HTTP Chức năng đám mây để chỉ cho phép người dùng xác thực Firebase?


140

Với chức năng đám mây căn cứ mới, tôi đã quyết định chuyển một số điểm cuối HTTP của mình sang căn cứ hỏa lực. Tất cả mọi thứ hoạt động tuyệt vời ... Nhưng tôi có vấn đề sau đây. Tôi có hai điểm cuối được xây dựng bởi HTTP Triggers (Hàm đám mây)

  1. Điểm cuối API để tạo người dùng và trả về Mã thông báo tùy chỉnh được tạo bởi SDK quản trị Firebase.
  2. Một điểm cuối API để lấy các chi tiết người dùng nhất định.

Mặc dù điểm cuối đầu tiên là tốt, nhưng đối với điểm cuối thứ hai của tôi, tôi chỉ muốn bảo vệ nó cho người dùng được xác thực. nghĩa là ai đó có mã thông báo tôi đã tạo trước đó.

Làm thế nào để tôi đi giải quyết điều này?

Tôi biết chúng ta có thể nhận được các tham số Tiêu đề trong hàm đám mây bằng cách sử dụng

request.get('x-myheader')

Nhưng có cách nào để bảo vệ điểm cuối giống như bảo vệ cơ sở dữ liệu thời gian thực không?


làm cách nào bạn có được mã thông báo tùy chỉnh được tạo bởi SDK quản trị Firebase trong API đầu tiên
Amine Harbaoui

2
@AmineHarbaoui Tôi cũng có câu hỏi tương tự. Xem trang này: firebase.google.com/docs/auth/admin/verify-id-tokens
MichM

Câu trả lời:


137

Có một mẫu mã chính thức cho những gì bạn đang cố gắng làm. Những gì nó minh họa là cách thiết lập chức năng HTTPS của bạn để yêu cầu tiêu đề Ủy quyền với mã thông báo mà khách hàng nhận được trong khi xác thực. Hàm sử dụng thư viện quản trị firebase để xác minh mã thông báo.

Ngoài ra, bạn có thể sử dụng "các chức năng có thể gọi được " để làm cho nhiều bản tóm tắt này dễ dàng hơn, nếu ứng dụng của bạn có thể sử dụng các thư viện máy khách Firebase.


2
Mẫu mã này có còn hiệu lực không? Đây vẫn là cách bạn sẽ giải quyết điều này ngày hôm nay?
Gal Bracha

1
@GalBracha Nó vẫn còn hiệu lực ngày hôm nay (31 tháng 10 năm 2017).
Doug Stevenson

@DougStevenson những cuộc gọi 'console.log' đó có ảnh hưởng 'đáng chú ý' nào đến hiệu suất không?
Sanka Darshana

1
Làm thế nào sẽ sử dụng các chức năng có thể gọi làm cho nồi hơi dễ dàng hơn? Từ những gì tôi hiểu đó chỉ là các chức năng máy chủ "không phải REST", tôi không thực sự hiểu chúng liên quan ở đây như thế nào. Cảm ơn.
1252748

2
@ 1252748 Nếu bạn đọc tài liệu được liên kết, nó sẽ trở nên rõ ràng. Nó tự động xử lý việc chuyển và xác thực mã thông báo xác thực, do đó bạn không phải viết mã đó ở hai bên.
Doug Stevenson

120

Như được đề cập bởi @Doug, bạn có thể sử dụng firebase-adminđể xác minh mã thông báo. Tôi đã thiết lập một ví dụ nhanh:

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

Trong ví dụ trên, tôi cũng đã bật CORS, nhưng đó là tùy chọn. Đầu tiên, bạn nhận đượcAuthorization tiêu đề và tìm hiểu token.

Sau đó, bạn có thể sử dụng firebase-adminđể xác minh mã thông báo đó. Bạn sẽ nhận được thông tin được giải mã cho người dùng đó trong phản hồi. Mặt khác, nếu mã thông báo không hợp lệ, nó sẽ gây ra lỗi.


13
Được nâng cấp vì nó đơn giản và không phụ thuộc vào express như ví dụ chính thức.
DarkNeuron

5
Bạn có thể giải thích thêm về các cors?
pete

@pete: cors chỉ là giải quyết chia sẻ tài nguyên nguồn gốc chéo. Bạn có thể google để biết thêm về nó.
Lạng Hoàng

@pete Cors cho phép bạn đạt điểm cuối của căn cứ hỏa lực đó từ các url khác nhau.
Walter Monecke

6
@RezaRahmati Bạn có thể sử dụng getIdToken()phương pháp trên tài liệu căn cứ hỏa lực phía máy khách (ví dụ firebase.auth().currentUser.getIdToken().then(token => console.log(token)))
Sẽ

18

Như @Doug đã đề cập, bạn có thể sử dụng Hàm có thể gọi được để loại trừ một số mã soạn sẵn khỏi máy khách và máy chủ của bạn.

Chức năng gọi được 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 };
});

Nó có thể được gọi trực tiếp từ khách hàng của bạn như vậy:

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

2

Các phương pháp trên xác thực người dùng bằng logic bên trong hàm, do đó, hàm vẫn phải được gọi để thực hiện kiểm tra.

Đó là một phương pháp hoàn toàn tốt, nhưng vì mục đích toàn diện, có một cách khác:

Bạn có thể đặt một chức năng là "riêng tư" để nó không thể được gọi ngoại trừ bởi người dùng đã đăng ký (bạn quyết định về quyền). Trong trường hợp này, các yêu cầu không được xác thực bị từ chối bên ngoài ngữ cảnh của hàm và hàm này hoàn toàn không được gọi.

Dưới đây là các tham chiếu đến (a) Định cấu hình các chức năng là công khai / riêng tư và sau đó (b) xác thực người dùng cuối với các chức năng của bạn .

Lưu ý rằng các tài liệu ở trên là dành cho Google Cloud Platform và thực tế, điều này hoạt động vì mọi dự án Firebase cũng là một dự án GCP. Một cảnh báo liên quan với phương pháp này là, khi viết, nó chỉ hoạt động với xác thực dựa trên tài khoản Google.


1

Có một ví dụ chính thức tuyệt vời về nó bằng Express - có thể sẽ hữu ích trong tương lai: https://github.com/firebase/fifts-samples/blob/master/authorized-https-endpoint/fifts/index.js (được dán bên dưới chắc chắn)

Hãy nhớ rằng exports.applàm cho các chức năng của bạn có sẵn trong /appslug (trong trường hợp này chỉ có một chức năng và có sẵn <you-firebase-app>/app/hello. Để loại bỏ nó, bạn thực sự cần phải viết lại phần Express một chút (phần trung gian để xác thực vẫn giữ nguyên - nó hoạt động rất tốt tốt và khá dễ hiểu nhờ ý kiến).

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

Viết lại của tôi để thoát khỏi /app:

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

module.exports = {
  hello
}

0

Tôi đã đấu tranh để có được xác thực căn cứ hỏa lực thích hợp trong chức năng GCP của golang. Thực tế không có ví dụ nào cho điều đó, vì vậy tôi đã quyết định xây dựng thư viện nhỏ này: https://github.com/Jblew/go-firebase-auth-in-gcp-fifts

Giờ đây, bạn có thể dễ dàng xác thực người dùng bằng cách sử dụng firebase-auth (khác với chức năng xác thực gcp và không được hỗ trợ trực tiếp bởi proxy nhận dạng nhận dạng).

Dưới đây là một ví dụ về việc sử dụng tiện ích:

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
}

Chỉ cần ghi nhớ để triển khai chức năng của bạn với --allow-unauthenticatedcờ (vì xác thực căn cứ hỏa lực xảy ra bên trong thực thi chức năng).

Hy vọng điều này sẽ giúp bạn vì nó đã giúp tôi. Tôi đã quyết định sử dụng golang cho các chức năng đám mây vì lý do hiệu suất - Jędrzej


0

Trong Firebase, để đơn giản hóa mã và công việc của bạn, đó chỉ là vấn đề thiết kế kiến ​​trúc :

  1. Đối với các trang web / nội dung có thể truy cập công khai , hãy sử dụng kích hoạt HTTPS vớiExpress . Để chỉ hạn chế samesite hoặc trang web cụ thể , hãy sử dụng CORSđể kiểm soát khía cạnh bảo mật này. Điều này có ý nghĩa vì Expresshữu ích cho SEO do nội dung kết xuất phía máy chủ của nó.
  2. Đối với các ứng dụng yêu cầu xác thực người dùng , hãy sử dụng Hàm Firebase có thể gọi được HTTPS , sau đó sử dụng contexttham số để lưu tất cả các rắc rối. Điều này cũng có ý nghĩa, bởi vì chẳng hạn như Ứng dụng một trang được xây dựng với AngularJS - AngularJS rất tệ cho SEO, nhưng vì đây là một ứng dụng được bảo vệ bằng mật khẩu, bạn cũng không cần nhiều SEO. Đối với việc tạo khuôn mẫu, AngularJS đã tích hợp sẵn khuôn mẫu, do đó không cần mẫu bên sever Express. Sau đó, chức năng có thể gọi Firebase là đủ tốt.

Với những điều trên, không còn rắc rối và làm cho cuộc sống dễ dàng hơn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.