在 App Engine 上使用 Firebase 驗證使用者

本教學課程說明如何使用 Firebase 驗證、App Engine 標準環境和Cloud Datastore 來擷取、驗證及儲存使用者憑證。

本文件將逐步說明如何使用 Firenotes 這個簡易的筆記工具應用程式,將使用者的筆記儲存在自己的個人筆記本中。筆記本會依個別使用者進行儲存,並透過每位使用者的唯一 Firebase 驗證 ID 加以識別。應用程式具有下列元件:

  • 前端設定登入使用者介面及擷取 Firebase 驗證 ID,也可處理驗證狀態變更並讓使用者查看其筆記。

  • FirebaseUI 是一套開放原始碼的置入式解決方案,可處理使用者登入、將多個供應商連結到一個帳戶、還原密碼及其他作業。它採用了驗證的最佳做法,提供流暢安全的登入體驗。

    FirebaseUI
  • 後端會驗證使用者的驗證狀態,並傳回使用者個人資料和其筆記。

這個應用程式使用 NDB 用戶端程式庫將使用者憑證儲存在 Cloud Datastore 中,但您可以將憑證儲存在自選資料庫中。

下圖顯示前端和後端之間的通訊方式,以及如何將使用者憑證從 Firebase 傳送到資料庫。
使用使用者憑證的要求路徑

Firenotes 以 Flask 網路應用程式架構為基礎。範例應用程式使用 Flask 是因為它簡單易用。但不論您使用哪一個架構,本文探討的概念和技術皆適用。

目標

完成本教學課程後,您將完成以下目標:

  • 設定 Firebase 驗證的使用者介面。
  • 取得 Firebase ID 代碼,並使用伺服器端驗證進行驗證。
  • 將使用者憑證和相關資料儲存在 Cloud Datastore 中。
  • 使用 NDB 用戶端程式庫查詢資料庫。
  • 將應用程式部署至 App Engine。

費用

本教學課程使用的 Google Cloud Platform (GCP) 收費元件如下:

  • Cloud Datastore

使用 Pricing Calculator 根據您的預測使用量來產生預估費用。 初次使用 GCP 的使用者可能符合免費試用的資格。

事前準備

  1. 安裝 GitPython 2.7virtualenv。如需進一步瞭解如何設定 Python 開發環境,例如安裝最新版本的 Python,請參閱替 GCP 設定 Python 開發環境的相關說明。
  2. 登入您的 Google 帳戶。

    如果您沒有帳戶,請申請新帳戶

  3. 選取或建立 Google Cloud Platform 專案。

    前往「Manage resources」(管理資源) 頁面

  4. 安裝並初始化 Cloud SDK

如果您已安裝 SDK 並將其初始化為其他專案,請將 gcloud 專案設為您用於 Firenotes 的 App Engine 專案 ID。請參閱管理 Cloud SDK 配置一文,瞭解使用 gcloud 工具更新專案會用到的特定指令。

複製範例應用程式

如要將範例應用程式下載到您的本機電腦上:

  1. 將範例應用程式存放區複製到本機電腦:

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git

    您也可以使用 ZIP 格式下載範例,然後解壓縮該檔案。

  2. 前往包含程式碼範例的目錄:

    cd python-docs-samples/appengine/standard/firebase/firenotes
    
    如何設定 FirebaseUI 並啟用識別資訊提供者:

  3. 按照下列步驟將 Firebase 新增至您的應用程式:

    1. Firebase 主控台中建立 Firebase 專案。
      • 如果您目前沒有 Firebase 專案,請按一下 [Add project] (新增專案),然後輸入現有的 Google Cloud Platform 專案名稱或新的專案名稱。
      • 如果您已有要使用的 Firebase 專案,請在主控台中選取該專案。
    2. 在專案總覽頁面中,按一下 [Add Firebase to your web app] (將 Firebase 加入您的網路應用程式)。如果您的專案已有應用程式,請在專案總覽頁面中選取 [Add App] (新增應用程式)
    3. 使用專案自訂程式碼片段的 Initialize Firebase 部分填寫 frontend/main.js 檔案的以下部分:

      // Obtain the following from the "Add Firebase to your web app" dialogue
      // Initialize Firebase
      var config = {
        apiKey: "<API_KEY>",
        authDomain: "<PROJECT_ID>.firebaseapp.com",
        databaseURL: "https://<DATABASE_NAME>.firebaseio.com",
        projectId: "<PROJECT_ID>",
        storageBucket: "<BUCKET>.appspot.com",
        messagingSenderId: "<MESSAGING_SENDER_ID>"
      };
  4. 編輯 backend/app.yaml 檔案,並在環境變數中輸入您的 Firebase 專案 ID:

    runtime: python27
    api_version: 1
    threadsafe: true
    service: backend
    
    handlers:
    - url: /.*
      script: main.app
    
    env_variables:
      # Replace with your Firebase project ID.
      FIREBASE_PROJECT_ID: '<PROJECT_ID>'
    
  5. frontend/main.js 檔案中選取您要提供給使用者的供應商,以設定 FirebaseUI 登入小工具

    // Firebase log-in widget
    function configureFirebaseLoginWidget() {
      var uiConfig = {
        'signInSuccessUrl': '/',
        'signInOptions': [
          // Leave the lines as is for the providers you want to offer your users.
          firebase.auth.GoogleAuthProvider.PROVIDER_ID,
          firebase.auth.FacebookAuthProvider.PROVIDER_ID,
          firebase.auth.TwitterAuthProvider.PROVIDER_ID,
          firebase.auth.GithubAuthProvider.PROVIDER_ID,
          firebase.auth.EmailAuthProvider.PROVIDER_ID
        ],
        // Terms of service url
        'tosUrl': '<your-tos-url>',
      };
    
      var ui = new firebaseui.auth.AuthUI(firebase.auth());
      ui.start('#firebaseui-auth-container', uiConfig);
    }
  6. 依序點選 [Authentication] (驗證) > [Sign-in method] (登入方式),啟用您選擇要保留在 Firebase 主控台中的供應商。然後,在「Sign-in providers」 (登入供應商) 下方,將游標懸停在供應商上方,然後按一下鉛筆圖示。

    登入供應商

    1. 開啟 [Enable] (啟用) 按鈕,然後針對第三方識別資訊提供者,輸入取自供應商開發人員網站的供應商 ID 和密碼。Firebase 文件會提供 FacebookTwitterGitHub 指南「事前準備」小節中的特定操作說明。啟用供應商後,按一下 [儲存]

      開啟啟用按鈕

    2. 在 Firebase 主控台上,於「Authorized Domains」 (已授權網域) 底下,按一下 [Add Domain] (新增網域),並以下列格式輸入您的應用程式在 App Engine 上的網域:

      [PROJECT_ID].appspot.com
      

      請勿在網域名稱前加上 http://

安裝依附元件

  1. 前往 backend 目錄,並且完成應用程式設定作業:

    cd backend/
    
  2. 將依附元件安裝到專案的 lib 目錄中:

    pip install -t lib -r requirements.txt
    
  3. appengine_config.py 中,vendor.add() 方法會註冊 lib 目錄中的資料庫。

在本機執行應用程式

如要在本機執行應用程式,請使用 App Engine 本機開發伺服器:

  1. main.js 中將下列網址新增為 backendHostURL

    http://localhost:8081

  2. 前往應用程式的根目錄,然後啟動開發伺服器:

    dev_appserver.py frontend/app.yaml backend/app.yaml
    
  3. 使用網路瀏覽器前往 http://localhost:8080/

在伺服器上驗證使用者

現在您已經設定了一個專案,並已初始化用於開發的應用程式,您可以瀏覽程式碼以瞭解如何在伺服器上擷取及驗證 Firebase ID 憑證。

從 Firebase 取得 ID 憑證

伺服器端驗證的第一步是擷取要驗證的存取憑證。系統會使用 Firebase 的 onAuthStateChanged() 接聽器處理驗證要求

firebase.auth().onAuthStateChanged(function(user) {
  if (user) {
    $('#logged-out').hide();
    var name = user.displayName;

    /* If the provider gives a display name, use the name for the
    personal welcome message. Otherwise, use the user's email. */
    var welcomeName = name ? name : user.email;

    user.getToken().then(function(idToken) {
      userIdToken = idToken;

      /* Now that the user is authenicated, fetch the notes. */
      fetchNotes();

      $('#user').text(welcomeName);
      $('#logged-in').show();

    });

  } else {
    $('#logged-in').hide();
    $('#logged-out').show();

  }

使用者登入後,回呼中的 Firebase getToken() 方法會傳回 Firebase ID 憑證,格式為 JSON Web Token (JWT)。

在伺服器上驗證憑證

使用者登入後,前端服務會透過 AJAX GET 要求擷取使用者筆記本中的任何現有筆記。這項作業需要獲得授權才能存取使用者的資料,因此 JWT 會在要求的 Authorization 標頭中使用 Bearer 結構定義傳送出去:

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  })

在用戶端可以存取伺服器資料之前,伺服器必須先驗證該憑證是否由 Firebase 簽署。您可以使用 Python 專用 Google 驗證程式庫來驗證這個憑證。使用驗證資料庫的 verify_firebase_token 函式來驗證不記名憑證及擷取相關宣告:

id_token = request.headers['Authorization'].split(' ').pop()
claims = google.oauth2.id_token.verify_firebase_token(
    id_token, HTTP_REQUEST)
if not claims:
    return 'Unauthorized', 401

每個識別資訊提供者都會傳送不同的一組宣告,但每組宣告至少會含有唯一使用者 ID 的 sub 宣告,以及用於提供某些個人資料資訊的宣告,如 nameemail,讓您為應用程式的使用者提供個人化使用體驗。

管理 Cloud Datastore 中的使用者資料

驗證使用者後,您必須儲存他們的資料,才能在登入工作階段結束後保留資料。以下幾節說明如何將筆記儲存為 Cloud Datastore 的「實體」,以及依使用者 ID 區隔實體。

建立實體以儲存使用者資料

您可以宣告含有整數或字串等特定屬性的 NDB 模型類別,在 Cloud Datastore 中建立實體。Cloud Datastore 會依「種類」建立實體索引;以 Firenotes 為例,每個實體的種類都是 Note。為方便查詢,每個 Note 會和「鍵名稱」一起儲存,這是從上一節的 sub 宣告中取得的使用者 ID。

以下程式碼會示範如何設定實體的屬性,包括在建立實體時,如何使用模型類別的建構函式方法進行設定,以及建立實體後,如何指派個別屬性的方式:

data = request.get_json()

# Populates note properties according to the model,
# with the user ID as the key name.
note = Note(
    parent=ndb.Key(Note, claims['sub']),
    message=data['message'])

# Some providers do not provide one of these so either can be used.
note.friendly_id = claims.get('name', claims.get('email', 'Unknown'))

如要將新建立的 Note 寫入 Cloud Datastore,請針對 note 物件呼叫 put() 方法。

擷取使用者資料

如要擷取與特定使用者 ID 相關的使用者資料,請使用 NDB 的 query() 方法搜尋資料庫,即可取得相同實體群組中的筆記。相同群組中的實體,或祖系路徑,會共用一個鍵名稱,在此範例中是指使用者 ID。

def query_database(user_id):
    """Fetches all notes associated with user_id.

    Notes are ordered them by date created, with most recent note added
    first.
    """
    ancestor_key = ndb.Key(Note, user_id)
    query = Note.query(ancestor=ancestor_key).order(-Note.created)
    notes = query.fetch()

    note_messages = []

    for note in notes:
        note_messages.append({
            'friendly_id': note.friendly_id,
            'message': note.message,
            'created': note.created
        })

    return note_messages

您可以接著擷取查詢資料,並在用戶端顯示筆記:

// Fetch notes from the backend.
function fetchNotes() {
  $.ajax(backendHostUrl + '/notes', {
    /* Set header for the XMLHttpRequest to get data from the web server
    associated with userIdToken */
    headers: {
      'Authorization': 'Bearer ' + userIdToken
    }
  }).then(function(data){
    $('#notes-container').empty();
    // Iterate over user data to display user's notes from database.
    data.forEach(function(note){
      $('#notes-container').append($('<p>').text(note.message));
    });
  });
}

部署應用程式

您已成功整合 Firebase 驗證與 App Engine 應用程式。如要查看在即時實際工作環境中執行的應用程式:

  1. main.js 中的後端主機網址變更為 https://backend-dot-[PROJECT_ID].appspot.com。用您的專案 ID 取代 [PROJECT_ID]
  2. 使用 Cloud SDK 指令列介面部署應用程式:

    gcloud app deploy backend/index.yaml frontend/app.yaml backend/app.yaml
    
  3. 前往 https://[PROJECT_ID].appspot.com 查看應用程式的實際執行情況。

清除

如要避免系統向您的 GCP 帳戶收取您在本教學課程中所用資源的相關費用,請刪除您的 App Engine 專案:

刪除專案

如要避免付費,最簡單的方法就是刪除您為了本教學課程所建立的專案。

如要刪除專案,請執行以下操作:

  1. 前往 GCP 主控台的「Projects」(專案) 頁面。

    前往「Projects」(專案) 頁面

  2. 在專案清單中,找到您要刪除的專案並按一下「刪除」圖示 delete
  3. 在對話方塊中輸入專案 ID,按一下 [Shut down] (關閉) 即可刪除專案。

後續步驟

本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁
Python 2 適用的 App Engine 標準環境