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

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

  • Измените клиент вашего приложения так, чтобы он отправлял токен App Check вместе с каждым запросом на ваш сервер, как описано на страницах для iOS+ , Android , веб-страниц , 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 , для проверки токенов проверки приложений.

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

  1. Получите набор общедоступных веб-ключей JSON (JWK) Firebase App Check из конечной точки JWKS App Check: https://firebaseappcheck.googleapis.com/v1/jwks .
  2. Проверьте подпись токена проверки приложений, чтобы убедиться в его легитимности.
  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

Защита от повтора (бета)

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

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

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

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

    • Если вы инициализировали 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 и Интернета .