Weryfikowanie tokenów Sprawdzania aplikacji z niestandardowego backendu

Możesz używać App Check do ochrony niestandardowych zasobów backendu nienależących do Google, takich jak własny backend hostowany samodzielnie. Aby to zrobić, musisz wykonać obie te czynności:

  • Zmodyfikuj klienta aplikacji, aby wysyłał token App Check wraz z każdym żądaniem do backendu, zgodnie z opisem na stronach dotyczących iOS, Androida, internetu, Fluttera, Unity lub C++.
  • Zmodyfikuj backend, aby wymagał prawidłowego tokena App Check w każdym żądaniu, zgodnie z opisem na tej stronie.

Weryfikacja tokena

Aby zweryfikować tokeny App Check na backendzie, dodaj do punktów końcowych interfejsu API logikę, która wykonuje te czynności:

  • Sprawdź, czy każde żądanie zawiera token App Check.

  • Zweryfikuj token App Check za pomocą pakietu Admin SDK.

    Jeśli weryfikacja się powiedzie, pakiet Admin SDK zwróci zdekodowany token App Check. Pomyślna weryfikacja oznacza, że token pochodzi z aplikacji należącej do Twojego projektu Firebase.

Odrzucaj wszystkie prośby, które nie przejdą żadnej z tych kontroli. Przykład:

Node.js

Jeśli nie masz jeszcze zainstalowanego pakietu SDK do administracji Node.js, zrób to.

Następnie, na przykładzie elementu 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, zrób to.

Następnie w procedurach obsługi punktów końcowych interfejsu API wywołaj app_check.verify_token() i odrzuć żądanie, jeśli się nie powiedzie. W tym przykładzie funkcja udekorowana za pomocą @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

Zainstaluj pakiet Admin SDK for Go, jeśli jeszcze go nie masz.

Następnie w procedurach obsługi punktów końcowych interfejsu API wywołaj funkcję appcheck.Client.VerifyToken() i odrzuć żądanie, jeśli się nie powiedzie. W tym przykładzie funkcja opakowująca dodaje tę logikę do 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 backend jest napisany w innym języku, możesz użyć ogólnej biblioteki JWT, np. tej dostępnej na stronie jwt.io, aby zweryfikować tokeny App Check.

Logika weryfikacji tokena musi wykonać te czynności:

  1. Pobierz publiczny zestaw kluczy internetowych JSON (JWK) usługi App Check w Firebase z punktu końcowego JWKS usługi App Check:https://firebaseappcheck.googleapis.com/v1/jwks
  2. Sprawdź podpis tokena Sprawdzania aplikacji, aby upewnić się, że jest on prawidłowy.
  3. Sprawdź, czy 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 Twoim projekcie.
  6. Sprawdź, czy token nie utracił ważności.
  7. Upewnij się, że odbiorcy tokena są zgodni z Twoim projektem.
  8. Opcjonalnie: sprawdź, czy podmiot tokena jest zgodny z identyfikatorem aplikacji.

Możliwości bibliotek JWT mogą się różnić. Pamiętaj, aby ręcznie wykonać wszystkie czynności, których nie obsługuje wybrana biblioteka.

W tym przykładzie wykonujemy niezbędne czynności w języku Ruby, używając gema jwt jako warstwy oprogramowania pośredniczącego 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 przed powtórzeniem (beta)

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

Korzystanie z ochrony przed powtórzeniem dodaje do wywołania verifyToken() rundę sieciową, a tym samym zwiększa opóźnienie każdego punktu końcowego, który z niej korzysta. Z tego powodu zalecamy włączanie ochrony przed atakami typu replay tylko w przypadku szczególnie wrażliwych punktów końcowych.

Aby korzystać z ochrony przed powtórzeniem:

  1. W Cloud Console przyznaj rolę „Weryfikator tokenów App Check w Firebase” kontu usługi używanemu do weryfikacji tokenów.

    • Jeśli pakiet Admin SDK został zainicjowany za pomocą pobranych z konsoli Firebase danych logowania konta usługi pakietu Admin SDK, wymagana rola jest już przyznana.
    • Jeśli używasz funkcji Cloud Functions 1 generacji z domyślną konfiguracją pakietu Admin SDK, przyznaj rolę domyślnemu kontu usługi App Engine. Zobacz Zmiana uprawnień konta usługi.
    • Jeśli używasz funkcji Cloud Functions 2 generacji z domyślną konfiguracją pakietu Admin SDK, przyznaj rolę domyślnemu kontu usługi Compute.
  2. Aby użyć tokena, przekaż { consume: true } do metody verifyToken() i sprawdź obiekt wyniku. Jeśli właściwość alreadyConsumed ma wartość true, odrzuć żądanie lub podejmij działania korygujące, np. zażądaj od wywołującego przejścia 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 weryfikację tokena i oznaczenie go jako wykorzystanego. Kolejne wywołania funkcji verifyToken(appCheckToken, { consume: true }) na tym samym tokenie ustawią wartość alreadyConsumed na true. (Pamiętaj, że verifyToken() nie odrzuca zużytego tokena ani nawet nie sprawdza, czy został on zużyty, jeśli nie jest ustawiona wartość consume).

Gdy włączysz tę funkcję w przypadku konkretnego punktu końcowego, musisz też zaktualizować kod klienta aplikacji, aby uzyskiwać jednorazowe tokeny o ograniczonym użyciu do wykorzystania w tym punkcie końcowym. Zapoznaj się z dokumentacją po stronie klienta dotyczącą platform Apple, Androidainternetu.