Verificare i token App Check da un backend personalizzato

Puoi utilizzare App Check per proteggere le risorse di backend personalizzate non Google per la tua app, ad esempio il tuo backend in hosting autonomo. Per farlo, devi eseguire entrambe le seguenti operazioni:

  • Modifica il client dell'app per inviare un token App Check insieme a ogni richiesta al tuo backend, come descritto nelle pagine per iOS+, Android, web, Flutter, Unity o C++.
  • Modifica il backend in modo che richieda un token App Check valido con ogni richiesta, come descritto in questa pagina.

Verifica del token

Per verificare i token App Check sul tuo backend, aggiungi ai tuoi endpoint API una logica che esegua le seguenti operazioni:

  • Verifica che ogni richiesta includa un token App Check.

  • Verifica il token App Check utilizzando l'SDK Admin.

    Se la verifica ha esito positivo, l'SDK Admin restituisce il token App Check decodificato. La verifica riuscita indica che il token proviene da un'app appartenente al tuo progetto Firebase.

Rifiuta qualsiasi richiesta che non supera uno dei due controlli. Ad esempio:

Node.js

Se non l'hai ancora fatto, installa l'SDK Admin Node.js.

Poi, utilizzando il middleware Express.js come esempio:

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

Se non l'hai ancora fatto, installa l'SDK Admin Python.

Poi, nei gestori degli endpoint API, chiama app_check.verify_token() e rifiuta la richiesta se non va a buon fine. Nell'esempio seguente, una funzione decorata con @before_request esegue questa attività per tutte le richieste:

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

Vai

Se non l'hai ancora fatto, installa l'SDK Admin per Go.

Poi, nei gestori degli endpoint API, chiama appcheck.Client.VerifyToken() e rifiuta la richiesta se non va a buon fine. Nell'esempio seguente, una funzione wrapper aggiunge questa logica ai gestori degli 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.
}

Altro

Se il tuo backend è scritto in un altro linguaggio, puoi utilizzare una libreria JWT per uso generico, come quella disponibile su jwt.io, per verificare i token App Check.

La logica di verifica del token deve completare i seguenti passaggi:

  1. Ottieni il set di chiavi web JSON (JWK) pubbliche di Firebase App Check dall'endpoint JWKS di App Check: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Verifica la firma del token App Check per assicurarti che sia legittima.
  3. Assicurati che l'intestazione del token utilizzi l'algoritmo RS256.
  4. Assicurati che l'intestazione del token sia di tipo JWT.
  5. Assicurati che il token sia emesso da Firebase App Check nel tuo progetto.
  6. Assicurati che il token non sia scaduto.
  7. Assicurati che il pubblico del token corrisponda al tuo progetto.
  8. Facoltativo: verifica che l'oggetto del token corrisponda all'ID app della tua app.

Le funzionalità delle librerie JWT possono variare. Assicurati di completare manualmente tutti i passaggi non gestiti dalla libreria che scegli.

L'esempio seguente esegue i passaggi necessari in Ruby utilizzando la gemma jwt come livello 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

Protezione di riproduzione (beta)

Per proteggere un endpoint da attacchi di tipo replay, puoi utilizzare il token App Check dopo averlo verificato in modo che possa essere utilizzato una sola volta.

L'utilizzo della protezione di riproduzione aggiunge un round trip di rete alla chiamata di verifica del token, aumentando quindi la latenza di qualsiasi endpoint che la utilizza. Per questo motivo, ti consigliamo di attivare la protezione dai replay solo su endpoint particolarmente sensibili.

Per utilizzare la protezione dai replay:

  1. Nella console Cloud, concedi il ruolo "Firebase App Check Token Verifier" al service account utilizzato per verificare i token.

    • Se hai inizializzato l'SDK Admin con le credenziali del service account dell'SDK Admin scaricate dalla console Firebase, il ruolo richiesto è già stato concesso.
    • Se utilizzi Cloud Functions di 1ª generazione con la configurazione predefinita dell'SDK Admin, concedi il ruolo al service account predefinito di App Engine. Consulta Modifica delle autorizzazioni del service account.
    • Se utilizzi Cloud Functions di 2ª generazione con la configurazione predefinita dell'SDK Admin, concedi il ruolo al service account Compute predefinito.
  2. Per utilizzare un token, passa { consume: true } (Node.js) o consume=True (Python) alla chiamata di verifica del token, quindi esamina l'oggetto del risultato. Se il token è già stato utilizzato, rifiuta la richiesta o intraprendi un'azione correttiva, ad esempio richiedi al chiamante di superare altri controlli:

    Node.js

    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.
    

    In questo modo, il token viene verificato e contrassegnato come utilizzato. Le chiamate future di verifyToken(appCheckToken, { consume: true }) sullo stesso token impostano alreadyConsumed su true. (Tieni presente che verifyToken() non rifiuta un token utilizzato e non controlla nemmeno se è stato utilizzato se consume non è impostato.)

    Python

    app_check_claims = app_check.verify_token(app_check_token, consume=True)
    
    if app_check_claims.get("already_consumed"):
        return "App Check token already consumed", 401
    
    # If verify_token() succeeds and already_consumed is not set, okay to continue.
    

    In questo modo, il token viene verificato e contrassegnato come utilizzato. Le chiamate future di app_check.verify_token(app_check_token, consume=True) sullo stesso token impostano already_consumed su True. (Tieni presente che app_check.verify_token() non rifiuta un token utilizzato o non controlla nemmeno se è stato utilizzato se consume non è impostato.)

Quando abiliti questa funzionalità per un determinato endpoint, devi anche aggiornare il codice client dell'app per acquisire token a uso limitato utilizzabili con l'endpoint. Consulta la documentazione lato client per piattaforme Apple, Android e web.