Weryfikowanie tokenów Sprawdzania aplikacji z niestandardowego backendu

Zasoby aplikacji inne niż Firebase, takie jak hostowane lokalnie backendy, możesz chronić za pomocą App Check. Aby to zrobić, musisz wykonać te 2 czynności:

  • Zmodyfikuj klienta aplikacji, aby wysyłał token App Check wraz z każdą prośbą do backendu, zgodnie z instrukcjami na stronach dotyczących iOS+, Androidasieci.
  • Zmodyfikuj backend, aby wymagać prawidłowego tokena App Check przy każdym żądaniu, jak opisano na tej stronie.

Weryfikacja tokena

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

  • 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 odkodowany token App Check. Pomyślna weryfikacja wskazuje, że token pochodzi z aplikacji należącej do Twojego projektu Firebase.

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

Node.js

Zainstaluj pakiet Node.js Admin SDK, jeśli jeszcze go nie masz.

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

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

Następnie w obsługach punktów końcowych interfejsu API wywołaj funkcję app_check.verify_token() i odrzucaj żądanie, jeśli się nie powiedzie. W tym przykładzie funkcja ozdobiona oznaczeniem @before_request wykonuje to zadanie dla 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 dla Go, jeśli jeszcze tego nie zrobiono.

Następnie w obsługach punktów końcowych interfejsu API wywołaj funkcję appcheck.Client.VerifyToken()i odrzucaj żą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ć biblioteki JWT ogólnego przeznaczenia, takiej jak ta dostępna na stronie jwt.io, aby zweryfikować tokeny App Check.

Logika weryfikacji tokena musi wykonać te czynności:

  1. Uzyskaj publiczny zestaw kluczy internetowych JSON (JWK) usługi Firebase App Check z punktu końcowego JWKS usługi:https://firebaseappcheck.googleapis.com/v1/jwks
  2. Sprawdź podpis tokena Sprawdzania aplikacji, aby upewnić się, że jest on prawidłowy.
  3. Upewnij się, że nagłówek tokena używa algorytmu RS256.
  4. Upewnij się, że nagłówek tokena ma typ JWT.
  5. Upewnij się, że token został wydany przez Sprawdzanie aplikacji Firebase w Twoim projekcie.
  6. Upewnij się, że token nie wygasł.
  7. Upewnij się, że odbiorcy tokena pasują do Twojego projektu.
  8. Opcjonalnie: sprawdź, czy podmiot tokenu 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 Ruby, używając gema jwt jako warstwy pośredniczącej 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 ponownym odtwarzaniem (beta)

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

Korzystanie z ochrony przed odtwarzaniem powoduje dodatkowy obrót sieci w przypadku wywołania verifyToken(), co zwiększa opóźnienie w przypadku każdego punktu końcowego, który z niej korzysta. Z tego powodu zalecamy włączenie ochrony przed atakami typu replay tylko na szczególnie wrażliwych punktach końcowych.

Aby korzystać z ochrony przed odtwarzaniem:

  1. W Cloud Console przyznaj roli „Weryfikator tokenów w aplikacji Firebase” konto usługi używane do weryfikowania tokenów.

    • Jeśli zainicjowałeś/zainicjowałaś pakiet Admin SDK za pomocą danych logowania do konta usługi pakietu Admin SDK pobranych z konsoli Firebase, wymagana rola została już przyznana.
    • Jeśli używasz Cloud Functions pierwszej generacji z domyślną konfiguracją AdminSDK, przypisz tę rolę do domyślnego konta usługi App Engine. Zobacz Zmienianie uprawnień konta usługi.
    • Jeśli używasz Cloud Functions 2 generacji z domyślną konfiguracją Admin SDK, przypisz tę rolę do domyślnego konta usługi Compute.
  2. Następnie, aby użyć tokena, prześlij { consume: true } do metody verifyToken() i sprawdź obiekt wyniku. Jeśli właściwość alreadyConsumed ma wartość true, odrzuć żądanie lub wykonaj jakieś działanie naprawcze, np. wymagaj od wywołującego, aby przeszedł inne weryfikacje.

    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.
    

    W ten sposób weryfikujesz token, a następnie oznaczasz go jako wykorzystany. Przyszłe wywołania verifyToken(appCheckToken, { consume: true }) z tym samym tokenem ustawią wartość alreadyConsumed na true. (Pamiętaj, że verifyToken() nie odrzuca wykorzystanego tokena ani nie sprawdza, czy został on wykorzystany, jeśli parametr consume nie jest ustawiony).

Gdy włączysz tę funkcję w przypadku konkretnego punktu końcowego, musisz też zaktualizować kod klienta aplikacji, aby uzyskać tokeny do jednorazowego użytku, które będą używane z tym punktem końcowym. Zapoznaj się z dokumentacją po stronie klienta na platformy Apple, Androidinternet.