التحقّق من رموز "فحص التطبيق" من واجهة خلفية مخصَّصة

يمكنك حماية موارد تطبيقك غير التابعة لمنصّة Firebase، مثل الخلفيات المستضافة ذاتيًا، باستخدام App Check. لإجراء ذلك، عليك تنفيذ ما يلي:

  • عدِّل برنامج تطبيقك لإرسال رمز App Check مع كل طلب إلى الخلفية، كما هو موضّح في صفحات iOS والإصدارات الأحدث و Android و الويب.
  • عدِّل الخلفية لتطلب الحصول على رمز App Check صالح مع كل طلب، كما هو موضّح في هذه الصفحة.

إثبات ملكية الرمز المميّز

لإثبات صحة علامات App Check في الخلفية، أضِف منطقًا إلى نقاط نهاية واجهة برمجة التطبيقات ينفّذ ما يلي:

  • تأكَّد من أنّ كل طلب يتضمّن رمز App Check.

  • تأكَّد من صحة رمز التفويض App Check باستخدام حزمة تطوير البرامج (SDK) للمشرف.

    في حال نجاح عملية التحقّق، تُعرِض حزمة Admin SDK الرمز المميّز App Check الذي تم فك تشفيره. يشير إتمام عملية التحقّق إلى أنّ الرمز المميّز مصدره تطبيق ينتمي إلى مشروعك على Firebase.

رفض أي طلب لا يجتاز أيّ من الفحصَين على سبيل المثال:

Node.js

ثبِّت حزمة تطوير البرامج (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

ثبِّت 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 لبرنامج 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، للتحقّق من رموز App Check المميّزة.

يجب أن يُكمِل منطق التحقّق من الرمز المميّز الخطوات التالية:

  1. احصل على مجموعة مفاتيح الويب JSON (JWK) العامة في Firebase App Check من نقطة نهاية مفاتيح الويب JSON لتطبيق 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 Console، منح دور "مُدقّق الرموز المميّزة لفحص التطبيقات في Firebase" لحساب الخدمة المستخدَم للتحقّق من الرموز المميّزة

    • إذا شغّلت حزمة Admin SDK باستخدام بيانات اعتماد حساب الخدمة لـ Admin SDK التي نزّلتها من وحدة تحكّم Firebase، يعني ذلك أنّه سبق أن تم منح الدور المطلوب.
    • إذا كنت تستخدم الجيل الأول من Cloud Functions مع الإعدادات التلقائية لحزمة Admin SDK، امنح الدور إلى حساب الخدمة التلقائية في App Engine. راجِع تغيير أذونات حساب الخدمة.
    • إذا كنت تستخدم الجيل الثاني من Cloud Functions مع الإعدادات التلقائية لـ Admin SDK، امنح الدور إلى حساب الخدمة التلقائية لوحدة الحساب.
  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).

عند تفعيل هذه الميزة لنقطة نهاية معيّنة، عليك أيضًا تعديل رمز عميل تطبيقك للحصول على رموز مستهلكة ذات استخدام محدود لاستخدامها مع نقطة النهاية. اطّلِع على المستندات من جهة العميل لأنظمة التشغيل Apple و Android و الويب.