ยืนยันโทเค็น App Check จากแบ็กเอนด์ที่กำหนดเอง

คุณสามารถปกป้องทรัพยากรที่ไม่ใช่ Firebase ของแอป เช่น แบ็กเอนด์ที่โฮสต์ด้วยตนเอง ด้วย App Check ในการดำเนินการนี้ คุณจะต้องทำทั้ง 2 อย่างต่อไปนี้

  • แก้ไขไคลเอ็นต์ของแอปให้ส่งโทเค็น App Check ไปพร้อมกับคำขอแต่ละรายการ ลงในแบ็กเอนด์ของคุณ ตามที่อธิบายไว้ในหน้าสำหรับ iOS+ Android และ เว็บ
  • แก้ไขแบ็กเอนด์ให้ต้องมีโทเค็น App Check ที่ถูกต้องกับทุกคำขอ ตามที่อธิบายไว้ในหน้านี้

การยืนยันโทเค็น

หากต้องการยืนยันโทเค็น App Check ในแบ็กเอนด์ ให้เพิ่มตรรกะในปลายทาง API ที่ทำงานดังต่อไปนี้

  • ตรวจสอบว่าคำขอแต่ละรายการมีโทเค็น App Check

  • ยืนยันโทเค็น App Check โดยใช้ Admin 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

หากคุณยังไม่ได้ติดตั้ง 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.
    ...

Go

หากคุณยังไม่ได้ติดตั้ง Admin SDK for Go แบบนั้น

จากนั้น ในเครื่องจัดการปลายทาง API ให้เรียกใช้ appcheck.Client.VerifyToken() และปฏิเสธคำขอหากล้มเหลว ในตัวอย่างต่อไปนี้ Wrapper จะเพิ่มตรรกะนี้ลงในเครื่องจัดการปลายทาง

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 จากแอป ตรวจสอบปลายทาง JWKS: 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 App Check Token Verifier" ของบัญชีบริการ ที่ใช้ในการยืนยันโทเค็น

    • หากคุณเริ่มต้น Admin SDK ด้วยบัญชีบริการของ Admin SDK ข้อมูลเข้าสู่ระบบที่คุณดาวน์โหลดจากคอนโซล Firebase บทบาทที่จําเป็นคือ อนุญาตแล้ว
    • หากคุณใช้ Cloud Functions รุ่นที่ 1 กับผู้ดูแลระบบเริ่มต้น การกำหนดค่า SDK ให้บทบาทกับบริการ App Engine เริ่มต้น บัญชี ดูการเปลี่ยนสิทธิ์ของบัญชีบริการ
    • หากคุณใช้ Cloud Functions รุ่นที่ 2 กับผู้ดูแลระบบเริ่มต้น กำหนดค่า 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 และ เว็บ