درستی‌سنجی کردن کدهای App Check از باطن سفارشی

می‌توانید از App Check برای محافظت از منابع باطن سفارشی غیرGoogle برای برنامه‌تان استفاده کنید، مثل باطن خودمیزبان خودتان. برای انجام این کار، باید هر دو مورد زیر را انجام دهید:

  • کلاینت برنامه‌تان را اصلاح کنید تا همراه با هر درخواست به پسین شما، یک کد App Check ارسال کند، همان‌طور که در صفحه‌های مربوط به iOS+، Android، وب، Flutter، Unity، یا C++ شرح داده شده است.
  • همان‌طور که در این صفحه شرح داده شده است، باطن خود را به‌گونه‌ای اصلاح کنید که برای هر درخواست به یک کد App Check معتبر نیاز داشته باشد.

درستی‌سنجی کد

برای درستی‌سنجی کردن توکن‌های App Check در پس‌بک، منطقی به نقاط پایانی میانای برنامه‌سازی کاربردی خود اضافه کنید که کارهای زیر را انجام دهد:

  • بررسی کنید که هر درخواست شامل یک کد App Check باشد.

  • درستی‌سنجی کردن کد App Check بااستفاده از «سرپرست 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 Admin SDK را نصب نکرده‌اید، این کار را انجام دهید.

سپس، در گرداننده‌های نقطه پایانی میانای برنامه‌سازی کاربردی، 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.
    ...

رفتن

اگر قبلاً Admin SDK for Go را نصب نکرده‌اید، این کار را انجام دهید.

سپس، در گرداننده‌های نقطه پایانی میانای برنامه‌سازی کاربردی، 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 یافت می‌شود، برای درستی‌سنجی کردن نشان‌های «بررسی برنامه» استفاده کنید.

منطق درستی‌سنجی کد شما باید مراحل زیر را تکمیل کند:

  1. «مجموعه کلید وب JSON»‏ (JWK) عمومی «بررسی برنامه Firebase» را از نقطه پایانی JWKS «بررسی برنامه» دریافت کنید: https://firebaseappcheck.googleapis.com/v1/jwks
  2. امضای کد App Check را درستی‌سنجی کنید تا مطمئن شوید قانونی است.
  3. مطمئن شوید که سرصفحه کد از الگوریتم RS256 استفاده می‌کند.
  4. مطمئن شوید که سرایند کد از نوع JWT باشد.
  5. مطمئن شوید که توکن توسط «بررسی برنامه Firebase» تحت پروژه شما صادر شده باشد.
  6. مطمئن شوید که کد منقضی نشده باشد.
  7. مطمئن شوید که مخاطب توکن با پروژه شما مطابقت داشته باشد.
  8. اختیاری: بررسی کنید که موضوع کد با شناسه برنامه شما مطابقت داشته باشد.

قابلیت‌های کتابخانه‌های JWT می‌تواند متفاوت باشد؛ حتماً هر مرحله‌ای را که توسط کتابخانه انتخابی شما انجام نمی‌شود به‌صورت دستی تکمیل کنید.

مثال زیر مراحل لازم را در Ruby بااستفاده از jwt gem به‌عنوان لایه میان‌افزار 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

محافظت دربرابر بازپخش (بتا)

برای محافظت از نقطه پایانی دربرابر حملات بازپخش، می‌توانید پس‌از درستی‌سنجی کردن کد App Check، از آن استفاده کنید تا فقط یک‌بار قابل‌استفاده باشد.

استفاده از محافظت دربرابر بازپخش، یک رفت‌وبرگشت شبکه به تماس درستی‌سنجی کد اضافه می‌کند و بنابراین به هر نقطه پایانی که از آن استفاده می‌کند تأخیر اضافه می‌کند. به همین دلیل، توصیه می‌کنیم محافظت دربرابر بازپخش را فقط در نقاط پایانی بسیار حساس فعال کنید.

برای استفاده از محافظت دربرابر بازپخش، کارهای زیر را انجام دهید:

  1. در کنسول Cloud، نقش «تأییدکننده کد Firebase App Check» را به حساب سرویس مورداستفاده برای تأیید کدها اعطا کنید.

    • اگر Admin SDK را با اعتبارنامه حساب سرویس Admin SDK که از کنسول Firebase بارگیری کرده‌اید مقداردهی اولیه کرده باشید، نقش موردنیاز قبلاً اعطا شده است.
    • اگر از نسل اول Cloud Functions با پیکربندی پیش‌فرض «سرپرست SDK» استفاده می‌کنید، این نقش را به حساب خدماتی پیش‌فرض App Engine اعطا کنید. تغییر اجازه‌های حساب سرویس را ببینید.
    • اگر از نسل دوم Cloud Functions با پیکربندی پیش‌فرض Admin SDK استفاده می‌کنید، این نقش را به حساب سرویس محاسباتی پیش‌فرض اعطا کنید.
  2. برای مصرف کردن یک کد، { consume: true } (Node.js) یا consume=True (Python) را به تماس درستی‌سنجی کد ارسال کنید، و سپس شیء نتیجه را بررسی کنید. اگر از قبل از کد استفاده شده است، درخواست را رد کنید یا اقدام اصلاحی انجام دهید، مثلاً از تماس‌گیرنده بخواهید سایر بررسی‌ها را بگذراند:

    Node.js

    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. (توجه داشته باشید که اگر consume تنظیم نشده باشد، verifyToken() توکن مصرف‌شده را رد نمی‌کند یا حتی بررسی نمی‌کند که آیا مصرف شده است یا نه.)

    پایتون

    app_check_claims = app_check.verify_token(app_check_token, consume=True)
    
    if app_check_claims.get("already_consumed"):
        return "App Check token already consumed", 401
    
    # If verify_token() succeeds and already_consumed is not set, okay to continue.
    

    این کار کد را تأیید می‌کند و سپس آن را به‌عنوان مصرف‌شده پرچم‌گذاری می‌کند. فراخوانی‌های آتی app_check.verify_token(app_check_token, consume=True) در همان مجموعه کد already_consumed به True. (توجه داشته باشید که اگر consume تنظیم نشده باشد، app_check.verify_token() توکن مصرف‌شده را رد نمی‌کند یا حتی بررسی نمی‌کند که آیا مصرف شده است یا نه.)

وقتی این ویژگی را برای نقطه پایانی خاصی فعال می‌کنید، باید کد کارخواه برنامه‌تان را نیز به‌روز کنید تا توکن‌های با استفاده محدود مصرف‌شدنی را برای استفاده با نقطه پایانی دریافت کنید. اسناد سمت کارخواه را برای پلاتفرم‌های Apple، Android، و وب ببینید.