تحقق من App Check tokens من خلفية مخصصة

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

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

التحقق من الرمز

للتحقق من الرموز المميزة للتحقق من التطبيق على الواجهة الخلفية ، أضف منطقًا إلى نقاط نهاية واجهة برمجة التطبيقات التي تقوم بما يلي:

  • تحقق من أن كل طلب يتضمن رمز التحقق من التطبيق.

  • تحقق من رمز التحقق من التطبيق باستخدام Admin SDK.

    إذا نجح التحقق ، فإن Admin SDK تُرجع الرمز المميز للتحقق من التطبيق الذي تم فك ترميزه. يشير التحقق الناجح إلى أن الرمز المميز نشأ من تطبيق ينتمي إلى مشروع 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 ، فقم بذلك.

بعد ذلك ، في معالجات نقطة نهاية 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 for 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.
}

آخر

إذا كانت الواجهة الخلفية الخاصة بك مكتوبة بلغة أخرى ، فيمكنك استخدام مكتبة JWT للأغراض العامة ، مثل تلك الموجودة في jwt.io ، للتحقق من رموز التحقق من التطبيق.

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

  1. احصل على Firebase App Check العام JSON Web Key (JWK) المعين من نقطة نهاية JWKS App Check: https://firebaseappcheck.googleapis.com/v1/jwks
  2. تحقق من توقيع رمز التحقق من التطبيق للتأكد من شرعيته.
  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

إعادة الحماية (تجريبي)

لحماية نقطة نهاية من هجمات إعادة التشغيل ، يمكنك استخدام رمز التحقق من التطبيق بعد التحقق منه بحيث يمكن استخدامه مرة واحدة فقط.

يؤدي استخدام حماية إعادة التشغيل إلى إضافة رحلة ذهابًا وإيابًا عبر الشبكة إلى مكالمة verifyToken() ، وبالتالي يضيف زمن انتقال إلى أي نقطة نهاية تستخدمه. لهذا السبب ، نوصي بتمكين حماية إعادة التشغيل فقط على نقاط النهاية الحساسة بشكل خاص.

لاستخدام الحماية من إعادة التشغيل ، قم بما يلي:

  1. في وحدة التحكم السحابية ، امنح دور "Firebase App Check Token Verifier" لحساب الخدمة المستخدم للتحقق من الرموز المميزة.

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