Xác minh mã thông báo Kiểm tra ứng dụng qua một phần phụ trợ tuỳ chỉnh

Bạn có thể bảo vệ những tài nguyên không phải Firebase của ứng dụng, chẳng hạn như các chương trình phụ trợ tự lưu trữ, bằng tính năng Kiểm tra ứng dụng. Để thực hiện điều này, bạn cần phải thực hiện cả hai việc sau:

  • Sửa đổi ứng dụng khách của bạn để gửi mã thông báo Kiểm tra ứng dụng cùng với mỗi yêu cầu vào phần phụ trợ của mình, theo mô tả trên các trang dành cho iOS trở lên, Androidweb.
  • Sửa đổi phần phụ trợ của bạn để yêu cầu mã thông báo Kiểm tra ứng dụng hợp lệ với mọi yêu cầu, như mô tả trên trang này.

Xác minh mã thông báo

Để xác minh mã thông báo của tính năng Kiểm tra ứng dụng trên phần phụ trợ, hãy thêm logic vào điểm cuối API thực hiện những việc sau:

  • Kiểm tra để đảm bảo mỗi yêu cầu đều có mã thông báo Kiểm tra ứng dụng.

  • Xác minh mã thông báo Kiểm tra ứng dụng bằng SDK dành cho quản trị viên.

    Nếu xác minh thành công, SDK dành cho quản trị viên sẽ trả về quy trình Kiểm tra ứng dụng đã giải mã mã thông báo. Xác minh thành công cho biết mã thông báo bắt nguồn từ một ứng dụng thuộc về dự án Firebase của bạn.

Từ chối bất kỳ yêu cầu nào không vượt qua được quy trình kiểm tra. Ví dụ:

Node.js

Nếu bạn chưa cài đặt SDK quản trị Node.js, làm như vậy.

Sau đó, sử dụng phần mềm trung gian Express.js làm ví dụ:

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

Nếu bạn chưa cài đặt SDK quản trị viên Python, làm như vậy.

Sau đó, trong trình xử lý thiết bị đầu cuối API, hãy gọi app_check.verify_token() và từ chối yêu cầu nếu yêu cầu đó không thành công. Trong ví dụ sau, một hàm được trang trí bằng @before_request sẽ thực hiện tác vụ này cho tất cả yêu cầu:

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.
    ...

Tiến hành

Nếu bạn chưa cài đặt SDK dành cho quản trị viên cho Go, làm như vậy.

Sau đó, trong trình xử lý thiết bị đầu cuối API, hãy gọi appcheck.Client.VerifyToken() và từ chối yêu cầu nếu không thành công. Trong ví dụ sau, một trình bao bọc sẽ thêm logic này vào trình xử lý điểm cuối:

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.
}

Khác

Nếu chương trình phụ trợ của bạn được viết bằng ngôn ngữ khác, bạn có thể sử dụng thư viện JWT đa năng, chẳng hạn như thư viện JWT có tại jwt.io để xác minh mã thông báo Kiểm tra ứng dụng.

Logic xác minh mã thông báo của bạn phải hoàn tất các bước sau:

  1. Lấy Bộ khoá web JSON công khai của tính năng Kiểm tra ứng dụng Firebase (JWK) từ Ứng dụng Kiểm tra điểm cuối JWKS: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Xác minh chữ ký của mã thông báo Kiểm tra ứng dụng để đảm bảo chữ ký là hợp lệ.
  3. Đảm bảo rằng tiêu đề của mã thông báo sử dụng thuật toán RS256.
  4. Đảm bảo rằng tiêu đề của mã thông báo có loại JWT.
  5. Đảm bảo rằng mã thông báo do tính năng Kiểm tra ứng dụng Firebase cấp theo dự án.
  6. Đảm bảo rằng mã thông báo chưa hết hạn.
  7. Đảm bảo rằng đối tượng của mã thông báo khớp với dự án của bạn.
  8. Không bắt buộc: Kiểm tra để đảm bảo rằng tiêu đề của mã thông báo khớp với Mã ứng dụng của ứng dụng.

Khả năng của thư viện JWT có thể khác nhau; hãy nhớ hoàn thành theo cách thủ công bất kỳ bước nào không do thư viện mà bạn chọn xử lý.

Ví dụ sau đây thực hiện các bước cần thiết trong Ruby bằng cách sử dụng jwt dưới dạng lớp phần mềm trung gian trong Rack.

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

Chống phát lại (thử nghiệm)

Để bảo vệ một điểm cuối khỏi các cuộc tấn công phát lại, bạn có thể sử dụng mã thông báo Kiểm tra ứng dụng sau khi xác minh để chỉ sử dụng được một lần.

Việc sử dụng tính năng chống phát lại sẽ thêm một lượt khứ hồi mạng vào verifyToken() và do đó thêm độ trễ cho bất kỳ điểm cuối nào sử dụng hàm đó. Vì lý do này, bạn chỉ nên bật tính năng chống phát lại trên các trang web đặc biệt nhạy cảm điểm cuối.

Để sử dụng tính năng chống phát lại, hãy làm như sau:

  1. Trong bảng điều khiển Cloud, cấp "Trình xác minh mã thông báo của tính năng Kiểm tra ứng dụng Firebase" vai trò đối với tài khoản dịch vụ được dùng để xác minh mã thông báo.

    • Nếu bạn đã khởi chạy SDK dành cho quản trị viên bằng tài khoản dịch vụ SDK dành cho quản trị viên thông tin đăng nhập mà bạn đã tải xuống từ bảng điều khiển của Firebase, vai trò bắt buộc là đã được cấp.
    • Nếu bạn đang sử dụng Cloud Functions thế hệ thứ nhất với vai trò Quản trị viên mặc định Cấu hình SDK, cấp vai trò cho dịch vụ mặc định của App Engine tài khoản. Hãy xem phần Thay đổi quyền đối với tài khoản dịch vụ.
    • Nếu bạn đang sử dụng Cloud Functions thế hệ thứ 2 với quyền Quản trị viên mặc định Cấu hình SDK, cấp vai trò cho Dịch vụ điện toán mặc định tài khoản.
  2. Sau đó, để sử dụng một mã thông báo, hãy truyền { consume: true } vào verifyToken() và kiểm tra đối tượng kết quả; nếu thuộc tính alreadyConsumedtrue, từ chối yêu cầu này hoặc thực hiện một số biện pháp khắc phục, chẳng hạn như yêu cầu phương thức gọi vượt qua các bước kiểm tra khác.

    Ví dụ:

    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.
    

    Thao tác này sẽ xác minh mã thông báo, sau đó gắn cờ là đã sử dụng. Lời gọi trong tương lai verifyToken(appCheckToken, { consume: true }) trên cùng một mã thông báo sẽ đặt alreadyConsumed thành true. (Xin lưu ý rằng verifyToken() không từ chối mã thông báo đã sử dụng hoặc thậm chí kiểm tra xem giá trị đó sẽ được sử dụng nếu bạn không đặt consume.)

Khi bật tính năng này cho một điểm cuối cụ thể, bạn cũng phải cập nhật mã ứng dụng khách ứng dụng của bạn để lấy mã thông báo sử dụng có giới hạn tiêu dùng để dùng với điểm cuối. Xem tài liệu phía máy khách cho các nền tảng của Apple, Androidweb.