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

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

  • کلاینت برنامه خود را طوری تغییر دهید که یک توکن App Check token) همراه با هر درخواست به بک‌اند شما ارسال کند، همانطور که در صفحات مربوط به iOS+ ، اندروید ، وب ، فلاتر ، یونیتی یا ++C توضیح داده شده است.
  • همانطور که در این صفحه توضیح داده شده است، backend خود را طوری تغییر دهید که برای هر درخواست، یک توکن App Check معتبر لازم باشد.

تأیید توکن

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

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

  • توکن App Check با استفاده از Admin SDK تأیید کنید.

    اگر تأیید موفقیت‌آمیز باشد، Admin SDK توکن رمزگشایی‌شده‌ی App Check برمی‌گرداند. تأیید موفقیت‌آمیز نشان می‌دهد که توکن از یک برنامه‌ی متعلق به پروژه‌ی Firebase شما سرچشمه گرفته است.

هر درخواستی را که با هر یک از این دو بررسی ناموفق باشد، رد کنید. برای مثال:

نود جی اس

اگر قبلاً SDK مربوط به محیط مدیریت Node.js را نصب نکرده‌اید، این کار را انجام دهید.

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

سپس، در کنترل‌کننده‌های نقطه پایانی 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.
    ...

برو

اگر هنوز Admin SDK را برای Go نصب نکرده‌اید، این کار را انجام دهید.

سپس، در کنترل‌کننده‌های نقطه پایانی 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.
}

دیگر

اگر backend شما به زبان دیگری نوشته شده است، می‌توانید از یک کتابخانه JWT همه منظوره، مانند کتابخانه‌ای که در jwt.io یافت می‌شود، برای تأیید توکن‌های App Check استفاده کنید.

منطق تأیید توکن شما باید مراحل زیر را طی کند:

  1. مجموعه کلید وب عمومی JSON Check مربوط به Firebase App Check (JWK) را از نقطه پایانی JWKS مربوط به App Check دریافت کنید: https://firebaseappcheck.googleapis.com/v1/jwks
  2. امضای توکن App Check را تأیید کنید تا از قانونی بودن آن اطمینان حاصل شود.
  3. مطمئن شوید که هدر توکن از الگوریتم RS256 استفاده می‌کند.
  4. مطمئن شوید که هدر توکن از نوع JWT باشد.
  5. مطمئن شوید که توکن توسط Firebase App Check تحت پروژه شما صادر شده است.
  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 را مصرف کنید تا فقط یک بار قابل استفاده باشد.

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

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

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

    • اگر SDK مدیریت را با اعتبارنامه‌های حساب سرویس SDK مدیریت که از کنسول Firebase دانلود کرده‌اید، مقداردهی اولیه کرده باشید، نقش مورد نیاز از قبل اعطا شده است.
    • اگر از توابع ابری نسل اول با پیکربندی پیش‌فرض Admin SDK استفاده می‌کنید، این نقش را به حساب سرویس پیش‌فرض App Engine اعطا کنید. به بخش تغییر مجوزهای حساب سرویس مراجعه کنید.
    • اگر از توابع ابری نسل دوم با پیکربندی پیش‌فرض Admin SDK استفاده می‌کنید، این نقش را به حساب کاربری Default computing service اعطا کنید.
  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 تنظیم می‌کند. (توجه داشته باشید که verifyToken() یک توکن مصرف‌شده را رد نمی‌کند و حتی بررسی نمی‌کند که آیا در صورت تنظیم نشدن consume مصرف شده است یا خیر.)

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