從自訂後端驗證應用程式檢查令牌

您可以使用 App Check 保護應用程式的非 Firebase 資源,例如自託管後端。為此,您需要執行以下兩項操作:

  • 修改您的應用程式用戶端,將 App Check 令牌與每個請求一起傳送到後端,如iOS+Androidweb頁面上所述。
  • 修改您的後端,以便每個請求都需要有效的 App Check 令牌,如本頁所述。

令牌驗證

若要驗證後端上的 App Check 令牌,請在 API 端點新增執行下列操作的邏輯:

  • 檢查每個請求是否包含應用程式檢查令牌。

  • 使用 Admin SDK 驗證應用程式檢查令牌。

    如果驗證成功,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 的 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 金鑰 (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

重播保護(測試版)

為了保護端點免受重播攻擊,您可以在驗證 App Check 令牌後使用它,以便它只能使用一次。

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

若要使用重播保護,請執行下列操作:

  1. Cloud 控制台中,向用於驗證令牌的服務帳號授予「Firebase App Check Token Verifier」角色。

    • 如果您使用從 Firebase 控制台下載的 Admin SDK 服務帳戶憑證初始化了 Admin SDK,則已授予所需的角色。
    • 如果您使用具有預設 Admin SDK 設定的第一代 Cloud Functions,請將角色授予App Engine 預設服務帳戶。請參閱變更服務帳戶權限
    • 如果您使用具有預設 Admin SDK 設定的第二代 Cloud Functions,請將角色授予預設計算服務帳戶
  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 })將設定alreadyConsumedtrue 。 (請注意, verifyToken()不會拒絕消耗的令牌,甚至如果未設定消耗,也不會檢查它是否已consume 。)

當您為特定端點啟用此功能時,您還必須更新應用程式用戶端程式碼以取得可消耗的限制使用令牌以與端點一起使用。請參閱Apple 平台AndroidWeb的客戶端文件。