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

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

  • แก้ไขไคลเอ็นต์แอปเพื่อส่งโทเค็น App Check พร้อมกับคำขอแต่ละรายการ ไปยังแบ็กเอนด์ตามที่อธิบายไว้ในหน้าสำหรับ iOS+ Android เว็บ Flutter Unity หรือ C++
  • แก้ไขแบ็กเอนด์ให้ต้องใช้โทเค็น 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 สำหรับ 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 Web Key (JWK) สาธารณะของ App Check ใน Firebase จากปลายทาง 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 โดยใช้ Gem jwt เป็นเลเยอร์มิดเดิลแวร์ของ 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

การป้องกันการเล่นซ้ำ (เบต้า)

หากต้องการปกป้องปลายทางจากการโจมตีแบบใช้โทเค็นซ้ำ (Replay) คุณสามารถใช้โทเค็น App Check หลังจากยืนยันแล้วเพื่อให้ใช้ได้เพียงครั้งเดียว

การใช้การป้องกันการเล่นซ้ำจะเพิ่มการรับส่งข้อมูลในเครือข่ายไปยังverifyToken() การโทร และจึงเพิ่มเวลาในการตอบสนองให้กับอุปกรณ์ปลายทางที่ใช้การป้องกันนี้ ด้วยเหตุนี้ เราจึงขอแนะนำให้คุณเปิดใช้การป้องกันการเล่นซ้ำเฉพาะในอุปกรณ์ปลายทางที่มีความละเอียดอ่อนเป็นพิเศษ

หากต้องการใช้การป้องกันการเล่นซ้ำ ให้ทำดังนี้

  1. ในCloud Console ให้มอบบทบาท "เครื่องมือยืนยันโทเค็น App Check ของ Firebase" ให้กับบัญชีบริการ ที่ใช้เพื่อยืนยันโทเค็น

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