Проверка токенов проверки приложений из пользовательского бэкэнда

Вы можете использовать App Check для защиты не-Google пользовательских бэкенд-ресурсов для вашего приложения, например, вашего собственного бэкенда. Для этого вам нужно будет выполнить оба следующих действия:

  • Измените клиентское приложение так, чтобы оно отправляло токен App Check вместе с каждым запросом на ваш бэкэнд, как описано на страницах для iOS+ , Android , web , Flutter , Unity или C++ .
  • Измените свой бэкэнд так, чтобы он требовал действительный токен App Check при каждом запросе, как описано на этой странице.

Проверка токена

Чтобы проверить токены App Check на вашем сервере, добавьте в конечные точки API логику, которая выполняет следующие действия:

  • Убедитесь, что каждый запрос включает токен App Check .

  • Проверьте токен App Check с помощью Admin SDK.

    Если проверка прошла успешно, Admin SDK возвращает декодированный токен App Check . Успешная проверка указывает на то, что токен был получен из приложения, принадлежащего вашему проекту Firebase.

Отклонить любой запрос, который не проходит ни одну из проверок. Например:

Node.js

Если вы еще не установили Node.js Admin SDK , сделайте это.

Затем, используя в качестве примера промежуточное программное обеспечение 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 Admin SDK , сделайте это.

Затем в обработчиках конечных точек API вызовите app_check.verify_token() и отклоните запрос, если он не пройден. В следующем примере функция, декорированная @before_request , выполняет эту задачу для всех запросов:

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

Идти

Если вы еще не установили Admin SDK для Go , сделайте это.

Затем в обработчиках конечных точек API вызовите appcheck.Client.VerifyToken() и отклоните запрос, если он не будет выполнен. В следующем примере функция-обертка добавляет эту логику в обработчики конечных точек:

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

Другой

Если ваш бэкэнд написан на другом языке, вы можете использовать универсальную библиотеку JWT, например, ту, что можно найти на jwt.io , для проверки токенов App Check.

Логика проверки вашего токена должна выполнять следующие шаги:

  1. Получите набор открытых веб-ключей JSON (JWK) Firebase App Check из конечной точки JWKS App Check: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Проверьте подпись токена App Check, чтобы убедиться в его подлинности.
  3. Убедитесь, что заголовок токена использует алгоритм RS256.
  4. Убедитесь, что заголовок токена имеет тип JWT.
  5. Убедитесь, что токен выпущен Firebase App Check в рамках вашего проекта.
  6. Убедитесь, что срок действия токена не истек.
  7. Убедитесь, что аудитория токена соответствует вашему проекту.
  8. Необязательно : проверьте, соответствует ли тема токена идентификатору приложения вашего приложения.

Возможности библиотек JWT могут различаться; обязательно вручную выполните все шаги, не выполняемые выбранной вами библиотекой.

В следующем примере выполняются необходимые шаги в Ruby с использованием гема jwt в качестве промежуточного уровня 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

Защита от повторного воспроизведения (бета)

Чтобы защитить конечную точку от атак повторного воспроизведения, вы можете использовать токен App Check после его проверки, чтобы его можно было использовать только один раз.

Использование защиты от повторного воспроизведения добавляет сетевой циклический обход к вызову verifyToken() и, следовательно, добавляет задержку к любой конечной точке, которая его использует. По этой причине мы рекомендуем включать защиту от повторного воспроизведения только на особенно чувствительных конечных точках.

Чтобы использовать защиту от повторного воспроизведения, выполните следующие действия:

  1. В консоли Cloud предоставьте роль «Firebase App Check Token Verifier» учетной записи службы, используемой для проверки токенов.

    • Если вы инициализировали Admin SDK с учетными данными учетной записи службы Admin SDK, загруженными из консоли Firebase, требуемая роль уже предоставлена.
    • Если вы используете Cloud Functions 1-го поколения с конфигурацией Admin SDK по умолчанию, предоставьте роль учетной записи службы App Engine по умолчанию . См. Изменение разрешений учетной записи службы .
    • Если вы используете Cloud Functions 2-го поколения с конфигурацией Admin SDK по умолчанию, предоставьте роль учетной записи службы вычислений по умолчанию .
  2. Затем, чтобы использовать токен, передайте { consume: true } методу verifyToken() и проверьте объект результата; если свойство alreadyConsumed имеет значение true , отклоните запрос или предпримите какие-либо корректирующие действия, например, потребовав от вызывающей стороны пройти другие проверки.

    Например:

    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.
    

    Это проверяет токен и затем помечает его как использованный. Будущие вызовы verifyToken(appCheckToken, { consume: true }) для того же токена установят alreadyConsumed в true . (Обратите внимание, что verifyToken() не отклоняет использованный токен и даже не проверяет, использован ли он, если consume не установлен.)

При включении этой функции для определенной конечной точки необходимо также обновить код клиента приложения, чтобы получить потребляемые токены ограниченного использования для использования с конечной точкой. См. клиентскую документацию для платформ Apple , Android и веб .