Weryfikowanie tokenów Sprawdzania aplikacji z niestandardowego backendu

Możesz chronić zasoby aplikacji spoza Firebase, takie jak własne backendy, za pomocą Sprawdzania aplikacji. Aby to zrobić, musisz wykonać obie te czynności:

  • Zmodyfikuj klienta aplikacji, aby wysyłał token Sprawdzania aplikacji przy każdym żądaniu do backendu, w sposób opisany na stronach iOS+, Androida oraz sieć.
  • Zmodyfikuj backend tak, aby przy każdym żądaniu wymagany był prawidłowy token Sprawdzania aplikacji. jak opisano na tej stronie.

Weryfikacja tokena

Aby weryfikować tokeny Sprawdzania aplikacji w backendzie, dodaj logikę do punktów końcowych interfejsu API który:

  • Sprawdź, czy każde żądanie zawiera token Sprawdzania aplikacji.

  • Sprawdź token Sprawdzania aplikacji za pomocą pakietu Admin SDK.

    Jeśli weryfikacja się powiedzie, pakiet Admin SDK zwróci zdekodowane Sprawdzenie aplikacji token. Udana weryfikacja wskazuje, że token pochodzi z aplikacji które należą do Twojego projektu Firebase.

Odrzuć wszystkie prośby, które nie przejdą kontroli. Przykład:

Node.js

Jeśli nie masz jeszcze zainstalowanego pakietu Node.js Admin SDK, to zrobić.

Następnie użyj na przykład oprogramowania pośredniczącego 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

Jeśli nie masz jeszcze zainstalowanego pakietu Python Admin SDK, to zrobić.

Następnie w modułach obsługi punktów końcowych interfejsu API wywołaj app_check.verify_token() i odrzuć prośbę, jeśli się nie powiedzie. W poniższym przykładzie funkcja ozdobiona symbolem @before_request wykonuje to zadanie w przypadku wszystkich żądań:

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

Jeśli nie masz jeszcze zainstalowanego pakietu Admin SDK dla języka Go, to zrobić.

Następnie w modułach obsługi punktów końcowych interfejsu API wywołaj appcheck.Client.VerifyToken() i odrzucić prośbę, jeśli się nie powiedzie. W przykładzie poniżej kod dodaje tę logikę do modułów obsługi punktów końcowych:

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

Inne

Jeśli Twój backend jest napisany w innym języku, możesz użyć biblioteka JWT do zwykłych obciążeń, np. jwt.io, aby zweryfikować tokeny Sprawdzania aplikacji.

Logika weryfikacji tokena musi wykonać te czynności:

  1. Uzyskiwanie z aplikacji publicznego klucza internetowego JSON Sprawdzania aplikacji Firebase (JWK) Sprawdź punkt końcowy JWKS: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Sprawdź podpis tokena Sprawdzania aplikacji, aby upewnić się, że jest on wiarygodny.
  3. Upewnij się, że nagłówek tokena używa algorytmu RS256.
  4. Sprawdź, czy nagłówek tokena ma typ JWT.
  5. Sprawdź, czy token został wydany przez Sprawdzanie aplikacji Firebase w w projektach AI.
  6. Upewnij się, że token nie utracił ważności.
  7. Sprawdź, czy odbiorcy tokena odpowiadają Twojemu projektowi.
  8. Opcjonalnie: sprawdź, czy temat tokena odpowiada identyfikatorowi aplikacji.

Możliwości bibliotek JWT mogą być różne. pamiętaj, aby ręcznie wykonać czynności, które nie są obsługiwane przez wybraną bibliotekę.

Poniższy przykład pokazuje wykonanie wymaganych kroków w języku Ruby przy użyciu interfejsu jwt jako warstwę oprogramowania pośredniczącego w elementach typu 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

Ochrona ponownego odtwarzania (beta)

Aby chronić punkt końcowy przed atakami typu replay, możesz wykorzystać token Sprawdzania aplikacji po zweryfikowaniu, tak aby można było go użyć tylko raz.

Ochrona przed ponownym odtwarzaniem powoduje dodanie połączenia w obie strony z verifyToken() i powoduje wydłużenie czasu oczekiwania w przypadku każdego punktu końcowego, który z niego korzysta. Z tego powodu zalecamy włączanie ochrony przed ponownym odtwarzaniem tylko w przypadku i punktów końcowych.

Aby użyć ochrony przed ponownym odtwarzaniem:

  1. W Konsola Cloud, przyznać „Firebase App Check Token Verifier”, rolę dla konta usługi używane do weryfikacji tokenów.

    • Jeśli pakiet Admin SDK został zainicjowany za pomocą konta usługi Admin SDK dane logowania pobrane z konsoli Firebase, wymagana rola to już przyznane.
    • Jeśli używasz funkcji w Cloud Functions 1 generacji z domyślnym administratorem Konfiguracja pakietu SDK, przypisz rolę do domyślnej usługi App Engine . Zobacz Zmienianie uprawnień konta usługi.
    • Jeśli używasz funkcji w Cloud Functions 2 generacji z domyślnym administratorem Konfiguracja pakietu SDK; przypisz rolę do domyślnej usługi obliczeniowej .
  2. Następnie, aby wykorzystać token, przekaż { consume: true } do verifyToken() i badanie obiektu wynikowego; jeśli właściwość alreadyConsumed to true, odrzuć prośbę lub podejmij działania naprawcze, takie jak wymaganie od dzwoniącego zaliczenia innych testów.

    Przykład:

    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.
    

    Spowoduje to zweryfikowanie tokena, a następnie oznaczenie go jako wykorzystanego. Przyszłe wywołania verifyToken(appCheckToken, { consume: true }) na tym samym tokenie ustaw alreadyConsumed na true. (Pamiętaj, że verifyToken() nie odrzuca wykorzystywanego tokena ani nie sprawdza, czy zużywane, jeśli nie skonfigurowano consume).

Po włączeniu tej funkcji w konkretnym punkcie końcowym musisz też zaktualizować za pomocą swojego kodu klienta aplikacji, aby pozyskać tokeny o ograniczonym wykorzystaniu do wykorzystania punktu końcowego. Zapoznaj się z dokumentacją po stronie klienta dotyczącą: Platformy Apple, Androida oraz sieć.