Verificar os tokens do App Check de um back-end personalizado

É possível proteger os recursos do seu app que não são do Firebase, como back-ends auto-hospedados, com App Check. Para isso, será necessário realizar as ações a seguir:

  • Modifique o cliente do app para enviar um token do App Check junto a cada solicitação para o back-end, conforme descrito nas páginas para iOS+, Android e web.
  • Modifique seu back-end para exigir um token App Check válido em todas as solicitações, conforme descrito nesta página.

Verificação de token

Para verificar tokens App Check no back-end, adicione lógica aos endpoints de API que realize estas ações:

  • Verifique se cada solicitação inclui um token App Check.

  • Verifique o token App Check usando o SDK Admin.

    Se a verificação for bem-sucedida, o SDK Admin vai retornar o token App Check decodificado. A verificação indica que o token é originário de um app pertencente ao seu projeto do Firebase.

Rejeite qualquer solicitação que não seja aprovada em qualquer uma das verificações. Por exemplo:

Node.js

Se você ainda não instalou o SDK Admin para Node.js, faça isso.

Em seguida, usando o middleware Express.js como exemplo:

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

Caso ainda não tenha instalado o SDK Admin para Python, faça isso.

Em seguida, nos gerenciadores de endpoint da API, chame app_check.verify_token() e rejeite a solicitação se ela falhar. No exemplo a seguir, uma função decorada com @before_request executa essa tarefa para todas as solicitações:

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

Se você ainda não instalou o SDK Admin para Go, faça isso.

Em seguida, nos gerenciadores de endpoint da API, chame appcheck.Client.VerifyToken() e rejeite a solicitação se ela falhar. No exemplo a seguir, uma função wrapper adiciona essa lógica aos gerenciadores de 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.
}

Outro

Se o back-end for escrito em outra linguagem, use uma biblioteca JWT de uso geral, como a encontrada em jwt.io, para verificar os tokens do App Check.

Sua lógica de verificação de token precisa concluir as seguintes etapas:

  1. Consiga o conjunto de chaves JSON da Web (JWK) público do Firebase App Check definido pelo endpoint do JWKS do App Check: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Verifique a assinatura do token do App Check para garantir que ele é legítimo.
  3. Verifique se o cabeçalho do token usa o algoritmo RS256.
  4. Verifique se o cabeçalho do token tem o tipo JWT.
  5. Verifique se o token é emitido pelo Firebase App Check no seu projeto.
  6. Verifique se o token não expirou.
  7. Verifique se o público do token corresponde ao seu projeto.
  8. Opcional: verifique se o assunto do token corresponde ao ID do seu app.

Os recursos das bibliotecas JWT podem ser diferentes. Conclua manualmente as etapas não processadas pela biblioteca escolhida.

O exemplo a seguir executa as etapas necessárias no Ruby usando a gem jwt como uma camada de 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

Proteção contra repetição (Beta)

Para proteger um endpoint contra ataques de repetição, consuma o token do App Check depois de verificá-lo, para que ele possa ser usado apenas uma vez.

O uso da proteção contra repetição adiciona uma latência de rede à chamada verifyToken() e, portanto, a qualquer endpoint que a utiliza. Por esse motivo, recomendamos que você ative a proteção contra repetição apenas em endpoints particularmente confidenciais.

Para usar a proteção de repetição, faça o seguinte:

  1. No console do Cloud, conceda o papel "Verificador de tokens do Firebase App Check" à conta de serviço usada para verificar tokens.

    • Se você inicializou o SDK Admin com as credenciais da conta de serviço do SDK Admin transferidas por download do console do Firebase, o papel necessário já foi concedido.
    • Se você estiver usando o Cloud Functions de primeira geração com a configuração padrão do SDK Admin, conceda o papel à conta de serviço padrão do App Engine. Consulte Como alterar as permissões da conta de serviço.
    • Se você estiver usando o Cloud Functions de segunda geração com a configuração padrão do SDK Admin, conceda o papel à conta de serviço de computação padrão.
  2. Em seguida, para consumir um token, transmita { consume: true } para o método verifyToken() e examine o objeto do resultado. Se a propriedade alreadyConsumed for true, rejeite a solicitação ou realize algum tipo de ação corretiva, como solicitar que o autor da chamada passe por uma aprovação nas outras verificações.

    Por exemplo:

    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.
    

    Isso verifica o token e o sinaliza como consumido. As invocações futuras de verifyToken(appCheckToken, { consume: true }) no mesmo token definirão alreadyConsumed como true. Observe que verifyToken() não rejeita um token consumido nem mesmo verifica se ele é consumido se consume não estiver definido.

Quando você ativa esse recurso para um endpoint específico, também é necessário atualizar o código de cliente do app para adquirir tokens de uso limitado consumíveis para uso com o endpoint. Consulte os documentos do lado do cliente para plataformas da Apple, Android e Web.