Memverifikasi token App Check dari backend kustom

Anda dapat melindungi resource non-Firebase aplikasi, seperti backend yang dihosting sendiri dengan App Check. Untuk melaksanakannya, Anda harus melakukan kedua hal berikut:

  • Ubah klien aplikasi Anda untuk mengirim token App Check beserta setiap permintaan ke backend, seperti yang dijelaskan di halaman untuk iOS+, Android, dan web.
  • Ubah backend Anda agar mewajibkan token App Check yang valid untuk setiap permintaan, seperti yang dijelaskan di halaman ini.

Verifikasi token

Untuk memverifikasi token App Check di backend, tambahkan logika ke endpoint API Anda yang melakukan hal berikut:

  • Pastikan setiap permintaan menyertakan token App Check.

  • Verifikasi token App Check menggunakan Admin SDK.

    Jika verifikasi berhasil, Admin SDK akan menampilkan token App Check yang didekode. Verifikasi yang berhasil menunjukkan bahwa token berasal dari aplikasi dalam project Firebase Anda.

Tolak setiap permintaan yang gagal dalam pemeriksaan tersebut. Contoh:

Node.js

Harap instal Node.js Admin SDK jika Anda belum melakukannya.

Lalu, gunakan middleware Express.js sebagai contoh:

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

Jika Anda belum menginstal Python Admin SDK, Anda harus menginstalnya.

Kemudian, di pengendali endpoint API Anda, panggil app_check.verify_token() dan tolak permintaan tersebut jika gagal. Pada contoh berikut, fungsi yang dilengkapi dengan @before_request akan melakukan tugas ini untuk semua permintaan:

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

Harap instal Admin SDK untuk Go jika Anda belum melakukannya.

Kemudian, di pengendali endpoint API Anda, panggil appcheck.Client.VerifyToken() dan tolak permintaan jika gagal. Pada contoh berikut, fungsi wrapper akan menambahkan logika ini ke pengendali endpoint:

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.
}

Lainnya

Jika backend Anda ditulis dalam bahasa lain, Anda dapat menggunakan library JWT tujuan umum, seperti yang ditemukan di jwt.io, untuk memverifikasi token App Check.

Logika verifikasi token Anda harus menyelesaikan langkah-langkah berikut:

  1. Dapatkan Set Kunci Web JSON (JWK) publik Firebase App Check dari endpoint JWKS App Check: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Verifikasi tanda tangan token App Check untuk memastikan keabsahannya.
  3. Pastikan header token menggunakan algoritma RS256.
  4. Pastikan header token memiliki jenis JWT.
  5. Pastikan token dikeluarkan oleh Firebase App Check di bagian project Anda.
  6. Pastikan masa berlaku token belum berakhir.
  7. Pastikan audiens token cocok dengan project Anda.
  8. Opsional: Pastikan subjek token cocok dengan ID Aplikasi untuk aplikasi Anda.

Kemampuan library JWT dapat berbeda-beda; pastikan untuk menyelesaikan langkah-langkah yang tidak ditangani oleh library yang Anda pilih secara manual.

Contoh berikut melakukan langkah-langkah yang diperlukan di Ruby menggunakan gem jwt sebagai lapisan middleware 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

Perlindungan replay (beta)

Untuk melindungi endpoint dari replay secara masif, Anda dapat menggunakan token App Check setelah memverifikasinya sehingga hanya dapat digunakan satu kali.

Menggunakan perlindungan replay akan menambahkan perjalanan dua arah jaringan ke panggilan verifyToken(), sehingga menambahkan latensi ke endpoint yang menggunakannya. Karena alasan ini, sebaiknya aktifkan perlindungan replay hanya di endpoint yang sangat sensitif.

Untuk menggunakan perlindungan replay, lakukan tindakan berikut:

  1. Di Cloud Console, berikan peran "Firebase App Check Token Verifier" ke akun layanan yang digunakan untuk memverifikasi token.

    • Jika Anda menginisialisasi Admin SDK dengan kredensial akun layanan Admin SDK yang didownload dari Firebase console, peran yang diperlukan sudah diberikan.
    • Jika Anda menggunakan Cloud Functions generasi ke-1 dengan konfigurasi Admin SDK default, berikan peran ke akun layanan default App Engine. Lihat Mengubah izin akun layanan.
    • Jika Anda menggunakan Cloud Functions generasi ke-2 dengan konfigurasi Admin SDK default, berikan peran ke Akun layanan komputasi default.
  2. Kemudian, untuk menggunakan token, teruskan { consume: true } ke metode verifyToken()dan periksa objek hasil; jika properti alreadyConsumed adalah true, tolak permintaan atau lakukan beberapa tindakan korektif, seperti meminta pemanggil untuk lulus pemeriksaan lainnya.

    Contoh:

    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.
    

    Tindakan ini akan memverifikasi token, lalu menandainya sebagai telah digunakan. Pemanggilan verifyToken(appCheckToken, { consume: true }) selanjutnya pada token yang sama akan menetapkan alreadyConsumed ke true. (Perhatikan bahwa verifyToken() tidak akan menolak token yang dipakai atau bahkan memeriksa apakah token tersebut digunakan jika consume tidak ditetapkan.)

Saat mengaktifkan fitur ini untuk endpoint tertentu, Anda juga harus memperbarui kode klien aplikasi untuk mendapatkan token penggunaan terbatas yang dapat dipakai untuk digunakan dengan endpoint. Baca dokumen sisi klien untuk platform Apple, Android, dan web.