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ể dùng App Check để bảo vệ các tài nguyên phụ trợ tuỳ chỉnh không phải của Google cho ứng dụng của mình, chẳng hạn như phụ trợ tự lưu trữ của riêng bạn. Để làm như vậy, bạn cần thực hiện cả hai việc sau:

  • Sửa đổi ứng dụng khách để gửi mã thông báo App Check cùng với mỗi yêu cầu đến phần phụ trợ của bạn, như mô tả trên các trang dành cho iOS+, Android, web, Flutter, Unity hoặc C++.
  • Sửa đổi phần phụ trợ để yêu cầu mã thông báo App Check hợp lệ cho 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 App Check trên phần phụ trợ, hãy thêm logic vào các đ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 App Check.

  • Xác minh mã thông báo App Check bằng Admin SDK.

    Nếu xác minh thành công, Admin SDK sẽ trả về mã thông báo App Check đã giải mã. 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 dự án Firebase của bạn.

Từ chối mọi yêu cầu không vượt qua được một trong hai bước kiểm tra này. Ví dụ:

Node.js

Nếu bạn chưa cài đặt Node.js Admin SDK, hãy cài đặt ngay.

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 Python Admin SDK, hãy cài đặt ngay.

Sau đó, trong trình xử lý điểm 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ả cá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.
    ...

Go

Nếu bạn chưa cài đặt Admin SDK for Go, hãy cài đặt ngay.

Sau đó, trong trình xử lý điểm cuối API, hãy gọi appcheck.Client.VerifyToken() 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 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 phần phụ trợ của bạn được viết bằng một ngôn ngữ khác, bạn có thể sử dụng một thư viện JWT đa năng (chẳng hạn như thư viện có tại jwt.io) để xác minh mã thông báo App Check.

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 (JWK) công khai của tính năng Kiểm tra ứng dụng Firebase từ điểm cuối JWKS của tính năng Kiểm tra ứng dụng: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Xác minh chữ ký của mã thông báo App Check để đảm bảo mã thông báo đó hợp lệ.
  3. Đảm bảo tiêu đề của mã thông báo sử dụng thuật toán RS256.
  4. Đảm bảo 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 Firebase App Check phát hành trong dự án của bạn.
  6. Đảm bảo 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 đối tượng của mã thông báo khớp với mã ứng dụng của ứng dụng.

Các thư viện JWT có thể có những chức năng khác nhau; hãy nhớ hoàn tất theo cách thủ công mọi bước mà thư viện bạn chọn không 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 gem jwt làm lớp trung gian 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

Bảo vệ khỏi các cuộc tấn cô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 App Check sau khi xác minh để mã thông báo chỉ có thể được dùng một lần.

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

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

  1. Trong Cloud Console, hãy cấp vai trò "Trình xác minh mã thông báo Kiểm tra ứng dụng Firebase" cho tài khoản dịch vụ dùng để xác minh mã thông báo.

    • Nếu bạn đã khởi tạo Admin SDK bằng thông tin đăng nhập tài khoản dịch vụ Admin SDK mà bạn tải xuống từ bảng điều khiển Firebase, thì vai trò bắt buộc đã được cấp.
    • Nếu bạn đang sử dụng Cloud Functions thế hệ thứ nhất với cấu hình Admin SDK mặc định, hãy cấp vai trò cho tài khoản dịch vụ mặc định của App Engine. Xem phần Thay đổi quyền của tài khoản dịch vụ.
    • Nếu bạn đang sử dụng Cloud Functions thế hệ thứ 2 với cấu hình Admin SDK mặc định, hãy cấp vai trò cho Tài khoản dịch vụ điện toán mặc định.
  2. Sau đó, để sử dụng mã thông báo, hãy truyền { consume: true } đến phương thức verifyToken() và kiểm tra đối tượng kết quả; nếu thuộc tính alreadyConsumedtrue, hãy từ chối yêu cầu 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 rồi gắn cờ là đã sử dụng. Các lệnh gọi verifyToken(appCheckToken, { consume: true }) trong tương lai 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í không kiểm tra xem mã thông báo đó đã được sử dụng hay chưa 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 để có được các mã thông báo sử dụng một lần có thời hạn để sử 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.