從自訂後端驗證 App Check 權杖

您可以使用 App Check 保護應用程式的非 Firebase 資源,例如自行管理的後端。如要這樣做,您必須同時完成下列兩項操作:

  • 修改應用程式用戶端,讓其在每個要求中傳送 App Check 權杖至後端,如 iOS+Android網頁頁面所述。
  • 修改後端,要求每項要求都提供有效的 App Check 權杖,如本頁所述。

符記驗證

如要在後端驗證 App Check 權杖,請在 API 端點中加入邏輯,執行以下操作:

  • 檢查每個要求是否包含 App Check 權杖。

  • 使用 Admin SDK 驗證 App Check 權杖。

    如果驗證成功,Admin SDK 會傳回已解碼的 App Check 權杖。驗證成功表示權杖來自屬於 Firebase 專案的應用程式。

拒絕所有未通過檢查的要求。例如:

Node.js

如果您尚未安裝 Node.js Admin SDK,請先安裝。

接著,以 Express.js 中介軟體為例:

import express from "express";
import { initializeApp } from "firebase-admin/app";
import { getAppCheck } from "firebase-admin/app-check";

const expressApp = express();
const firebaseApp = initializeApp();

const appCheckVerification = async (req, res, next) => {
    const appCheckToken = req.header("X-Firebase-AppCheck");

    if (!appCheckToken) {
        res.status(401);
        return next("Unauthorized");
    }

    try {
        const appCheckClaims = await getAppCheck().verifyToken(appCheckToken);

        // If verifyToken() succeeds, continue with the next middleware
        // function in the stack.
        return next();
    } catch (err) {
        res.status(401);
        return next("Unauthorized");
    }
}

expressApp.get("/yourApiEndpoint", [appCheckVerification], (req, res) => {
    // Handle request.
});

Python

如果您尚未安裝 Python Admin SDK,請先安裝。

接著,在 API 端點處理常式中呼叫 app_check.verify_token(),並在要求失敗時拒絕要求。在以下範例中,以 @before_request 修飾的函式會為所有要求執行此工作:

import firebase_admin
from firebase_admin import app_check
import flask
import jwt

firebase_app = firebase_admin.initialize_app()
flask_app = flask.Flask(__name__)

@flask_app.before_request
def verify_app_check() -> None:
    app_check_token = flask.request.headers.get("X-Firebase-AppCheck", default="")
    try:
        app_check_claims = app_check.verify_token(app_check_token)
        # If verify_token() succeeds, okay to continue to route handler.
    except (ValueError, jwt.exceptions.DecodeError):
        flask.abort(401)

@flask_app.route("/yourApiEndpoint")
def your_api_endpoint(request: flask.Request):
    # Handle request.
    ...

Go

如果您尚未安裝 Go 適用的 Admin SDK,請先安裝。

接著,在 API 端點處理常式中呼叫 appcheck.Client.VerifyToken(),並在要求失敗時拒絕要求。在以下範例中,包裝函式會將此邏輯新增至端點處理常式:

package main

import (
    "context"
    "log"
    "net/http"

    firebaseAdmin "firebase.google.com/go/v4"
    "firebase.google.com/go/v4/appcheck"
)

var (
    appCheck *appcheck.Client
)

func main() {
    app, err := firebaseAdmin.NewApp(context.Background(), nil)
    if err != nil {
        log.Fatalf("error initializing app: %v\n", err)
    }

    appCheck, err = app.AppCheck(context.Background())
    if err != nil {
        log.Fatalf("error initializing app: %v\n", err)
    }

    http.HandleFunc("/yourApiEndpoint", requireAppCheck(yourApiEndpointHandler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func requireAppCheck(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    wrappedHandler := func(w http.ResponseWriter, r *http.Request) {
        appCheckToken, ok := r.Header[http.CanonicalHeaderKey("X-Firebase-AppCheck")]
        if !ok {
            w.WriteHeader(http.StatusUnauthorized)
            w.Write([]byte("Unauthorized."))
            return
        }

        _, err := appCheck.VerifyToken(appCheckToken[0])
        if err != nil {
            w.WriteHeader(http.StatusUnauthorized)
            w.Write([]byte("Unauthorized."))
            return
        }

        // If VerifyToken() succeeds, continue with the provided handler.
        handler(w, r)
    }
    return wrappedHandler
}

func yourApiEndpointHandler(w http.ResponseWriter, r *http.Request) {
    // Handle request.
}

其他

如果後端是以其他語言編寫,您可以使用通用 JWT 程式庫 (例如 jwt.io 中的程式庫) 驗證 App Check 權杖。

權杖驗證邏輯必須完成下列步驟:

  1. 從 App Check JWKS 端點取得 Firebase App Check 公開 JSON Web Key (JWK) 組合: https://firebaseappcheck.googleapis.com/v1/jwks
  2. 驗證 App Check 權杖的簽名,確保其合法性。
  3. 確認符記的標頭使用 RS256 演算法。
  4. 確認權杖的標頭類型為 JWT。
  5. 請確認權杖是由專案下的 Firebase App Check 核發。
  6. 確認權杖未過期。
  7. 確認符記的目標對象與專案相符。
  8. 選用:確認權杖的使用者與應用程式的應用程式 ID 相符。

JWT 程式庫的功能可能有所不同,請務必手動完成所選程式庫未處理的任何步驟。

以下範例會使用 jwt gem 做為 Rack 中介層,在 Ruby 中執行必要步驟。

require 'json'
require 'jwt'
require 'net/http'
require 'uri'

class AppCheckVerification
def initialize(app, options = {})
    @app = app
    @project_number = options[:project_number]
end

def call(env)
    app_id = verify(env['HTTP_X_FIREBASE_APPCHECK'])
    return [401, { 'Content-Type' => 'text/plain' }, ['Unauthenticated']] unless app_id
    env['firebase.app'] = app_id
    @app.call(env)
end

def verify(token)
    return unless token

    # 1. Obtain the Firebase App Check Public Keys
    # Note: It is not recommended to hard code these keys as they rotate,
    # but you should cache them for up to 6 hours.
    uri = URI('https://firebaseappcheck.googleapis.com/v1/jwks')
    jwks = JSON(Net::HTTP.get(uri))

    # 2. Verify the signature on the App Check token
    payload, header = JWT.decode(token, nil, true, jwks: jwks, algorithms: 'RS256')

    # 3. Ensure the token's header uses the algorithm RS256
    return unless header['alg'] == 'RS256'

    # 4. Ensure the token's header has type JWT
    return unless header['typ'] == 'JWT'

    # 5. Ensure the token is issued by App Check
    return unless payload['iss'] == "https://firebaseappcheck.googleapis.com/#{@project_number}"

    # 6. Ensure the token is not expired
    return unless payload['exp'] > Time.new.to_i

    # 7. Ensure the token's audience matches your project
    return unless payload['aud'].include? "projects/#{@project_number}"

    # 8. The token's subject will be the app ID, you may optionally filter against
    # an allow list
    payload['sub']
rescue
end
end

class Application
def call(env)
    [200, { 'Content-Type' => 'text/plain' }, ["Hello app #{env['firebase.app']}"]]
end
end

use AppCheckVerification, project_number: 1234567890
run Application.new

重播防護 (Beta 版)

為保護端點免於遭受重送攻擊,您可以在驗證 App Check 權杖後使用該權杖,這樣權杖就只能使用一次。

使用重播保護功能會為 verifyToken() 呼叫增加網路往返時間,因此會為使用該呼叫的任何端點增加延遲時間。因此,我們建議您只在特別敏感的端點啟用重播保護功能。

如要使用防重播機制,請執行下列操作:

  1. Cloud 控制台中,將「Firebase App Check Token Verifier」角色授予用於驗證權杖的服務帳戶。

    • 如果您使用從 Firebase 控制台下載的 Admin SDK 服務帳戶憑證,初始化 Admin SDK,則系統已授予必要的角色。
    • 如果您使用第 1 代 Cloud Functions 搭配預設的 Admin SDK 設定,請將角色授予 App Engine 預設服務帳戶。請參閱「變更服務帳戶權限」。
    • 如果您使用第 2 代 Cloud Functions 搭配預設的 Admin SDK 設定,請將角色授予預設運算服務帳戶
  2. 接著,如要使用權杖,請將 { consume: true } 傳遞至 verifyToken() 方法,並檢查結果物件;如果 alreadyConsumed 屬性為 true,請拒絕要求或採取某種修正動作,例如要求呼叫端通過其他檢查。

    例如:

    const appCheckClaims = await getAppCheck().verifyToken(appCheckToken, { consume: true });
    
    if (appCheckClaims.alreadyConsumed) {
        res.status(401);
        return next('Unauthorized');
    }
    
    // If verifyToken() succeeds and alreadyConsumed is not set, okay to continue.
    

    這會驗證權杖,然後將其標記為已使用。日後在同一個符記上叫用 verifyToken(appCheckToken, { consume: true }) 時,系統會將 alreadyConsumed 設為 true。(請注意,如果未設定 consumeverifyToken() 不會拒絕已使用的權杖,甚至不會檢查權杖是否已使用)。

為特定端點啟用這項功能時,您也必須更新應用程式用戶端程式碼,以便取得可消耗的限用權杖,用於與端點搭配使用。請參閱 Apple 平台Android網頁的用戶端說明文件。