Catch up on highlights from Firebase at Google I/O 2023. Learn more

Vérifier les jetons App Check à partir d'un backend personnalisé

Vous pouvez protéger les ressources non Firebase de votre application, telles que les backends auto-hébergés, avec App Check. Pour ce faire, vous devrez effectuer les deux opérations suivantes :

  • Modifiez votre client d'application pour envoyer un jeton App Check avec chaque demande à votre backend, comme décrit sur les pages pour iOS+ , Android et web .
  • Modifiez votre backend pour exiger un jeton App Check valide avec chaque demande, comme décrit sur cette page.

Vérification du jeton

Pour vérifier les jetons App Check sur votre backend, ajoutez une logique à vos points de terminaison d'API qui effectue les opérations suivantes :

  • Vérifiez que chaque requête inclut un jeton App Check.

  • Vérifiez le jeton App Check à l'aide du SDK Admin.

    Si la vérification réussit, le SDK Admin renvoie le jeton App Check décodé. Une vérification réussie indique que le jeton provient d'une application appartenant à votre projet Firebase.

Rejeter toute demande qui échoue à l'une ou l'autre des vérifications. Par exemple:

Node.js

Si vous n'avez pas encore installé le SDK d'administration Node.js , faites-le.

Ensuite, en utilisant le middleware Express.js comme exemple :

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

Si vous n'avez pas encore installé le SDK d'administration Python , faites-le.

Ensuite, dans vos gestionnaires de points de terminaison d'API, appelez app_check.verify_token() et rejetez la demande si elle échoue. Dans l'exemple suivant, une fonction décorée avec @before_request effectue cette tâche pour toutes les requêtes :

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

Aller

Si vous n'avez pas encore installé le SDK Admin pour Go , faites-le.

Ensuite, dans vos gestionnaires de point de terminaison d'API, appelez appcheck.Client.VerifyToken() et rejetez la demande si elle échoue. Dans l'exemple suivant, une fonction wrapper ajoute cette logique aux gestionnaires de point de terminaison :

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

Autre

Si votre backend est écrit dans un autre langage, vous pouvez utiliser une bibliothèque JWT à usage général, telle que celle trouvée sur jwt.io , pour vérifier les jetons App Check.

Votre logique de vérification de jeton doit suivre les étapes suivantes :

  1. Obtenez l'ensemble de clés Web JSON (JWK) publiques Firebase App Check à partir du point de terminaison App Check JWKS : https://firebaseappcheck.googleapis.com/v1/jwks
  2. Vérifiez la signature du jeton App Check pour vous assurer qu'il est légitime.
  3. Assurez-vous que l'en-tête du jeton utilise l'algorithme RS256.
  4. Assurez-vous que l'en-tête du jeton est de type JWT.
  5. Assurez-vous que le jeton est émis par Firebase App Check sous votre projet.
  6. Assurez-vous que le jeton n'a pas expiré.
  7. Assurez-vous que l'audience du jeton correspond à votre projet.
  8. Facultatif : vérifiez que l'objet du jeton correspond à l'ID d'application de votre application.

Les capacités des bibliothèques JWT peuvent différer ; assurez-vous d'effectuer manuellement toutes les étapes non gérées par la bibliothèque que vous choisissez.

L'exemple suivant exécute les étapes nécessaires dans Ruby en utilisant la gemme jwt comme couche 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

Protection contre la relecture (bêta)

Pour protéger un point de terminaison contre les attaques par relecture, vous pouvez consommer le jeton App Check après l'avoir vérifié afin qu'il ne puisse être utilisé qu'une seule fois.

L'utilisation de la protection contre la relecture ajoute un aller-retour réseau à l'appel de verifyToken() et ajoute donc de la latence à tout point de terminaison qui l'utilise. Pour cette raison, nous vous recommandons d'activer la protection contre la relecture uniquement sur les endpoints particulièrement sensibles.

Pour utiliser la protection contre la relecture, assurez-vous d'abord que le rôle IAM "Firebase App Check Token Verifier" est activé pour le compte de service que vous utilisez avec le SDK Admin. Dans Cloud Functions, il s'agit généralement du compte de service de calcul par défaut ; sur les autres plates-formes, il s'agit généralement du compte de service Admin SDK. Vous pouvez activer les rôles IAM dans la console Cloud .

Ensuite, pour consommer un jeton, passez { consume: true } à la méthode verifyToken() et examinez l'objet résultat ; si la propriété alreadyConsumed est true , rejetez la demande ou prenez une mesure corrective, telle que demander à l'appelant de passer d'autres vérifications.

Par exemple:

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.

Cela vérifiera le jeton, puis le signalera comme consommé. Les futures invocations de verifyToken(appCheckToken, { consume: true }) sur le même jeton définiront alreadyConsumed sur true . (Notez que verifyToken() ne rejettera pas un jeton consommé ni ne vérifiera même s'il est consommé si consume n'est pas défini.)

Lorsque vous activez cette fonctionnalité pour un point de terminaison particulier, vous devez également mettre à jour le code de votre client d'application pour acquérir des jetons consommables à usage limité à utiliser avec le point de terminaison. Consultez la documentation côté client pour les plates-formes Apple , Android et Web .