Verifica los tokens de la Verificación de aplicaciones en un backend personalizado

Mediante la Verificación de aplicaciones, puedes proteger recursos de la app que no son de Firebase, como los backends autoalojados. Para ello, deberás hacer lo siguiente:

  • Modifica el cliente de la app para que envíe un token de Verificación de aplicaciones junto con cada solicitud al backend, como se describe en las páginas de iOS+, Android y la Web.
  • Modifica tu backend para que requiera un token válido de Verificación de aplicaciones con cada solicitud, como se describe en esta página.

Verificación de tokens

Agrega lógica a los extremos de la API a fin de verificar los tokens de Verificación de aplicaciones en tu backend para que haga lo siguiente:

  • Compruebe que cada solicitud incluya un token de la Verificación de aplicaciones.

  • Verifica el token de la Verificación de aplicaciones con el SDK de Admin.

    Si la verificación se realiza correctamente, el SDK de Admin muestra el token de la Verificación de aplicaciones decodificado. La verificación correcta indica que el token se originó de una app que pertenece a tu proyecto de Firebase.

Rechaza cualquier solicitud que no cumpla con cualquiera de estos dos requisitos. Por ejemplo:

Node.js

Si aún no lo has hecho, instala el SDK de Admin de Node.js.

Luego, usa el middleware de Express.js como ejemplo de la siguiente manera:

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 aún no lo has hecho, instala el SDK de Admin de Python.

Luego, en los controladores de extremos de API, llama a app_check.verify_token() y rechaza la solicitud si falla. En el siguiente ejemplo, una función decorada con @before_request realiza esta tarea para todas las solicitudes:

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

Si aún no lo has hecho, instala el SDK de Admin para Go.

Luego, en los controladores de extremos de API, llama a appcheck.Client.VerifyToken() y rechaza la solicitud si falla. En el siguiente ejemplo, una función wrapper agrega esta lógica a los controladores de extremo:

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

Otros

Si tu backend está escrito en otro idioma, puedes usar una biblioteca JWT de uso general, como la que se encuentra enJWT libraryjwt.io, para verificar tokens de Verificación de aplicaciones.

Tu lógica de verificación de tokens debe completar los siguientes pasos:

  1. Obtén el conjunto público de claves web JSON (JWK) de la Verificación de aplicaciones de Firebase desde el extremo de JWKS de la Verificación de aplicaciones: https://firebaseappcheck.googleapis.com/v1/jwks
  2. Verifica la firma del token de la Verificación de aplicaciones para asegurarte de que sea legítima.
  3. Asegúrate de que el encabezado del token use el algoritmo RS256.
  4. Asegúrate de que el encabezado del token tenga el tipo JWT.
  5. Asegúrate de que la Verificación de aplicaciones de Firebase emita el token en tu proyecto.
  6. Asegúrate de que el token no haya vencido.
  7. Asegúrate de que el público del token coincida con tu proyecto.
  8. Opcional: Verifica que el asunto del token coincida con el ID de tu app.

Las capacidades de las bibliotecas JWT pueden ser diferentes. Asegúrate de completar manualmente los pasos que no maneja la biblioteca que elegiste.

En el siguiente ejemplo, se realizan los pasos necesarios en Ruby utilizando la gema jwt como una capa de middleware de 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

Protección contra la repetición (beta)

Para proteger un extremo de los ataques de repetición, puedes consumir el token de la Verificación de aplicaciones después de verificarlo para que pueda usarse solo una vez.

El uso de la protección contra la repetición agrega un proceso de ida y vuelta en la red a la llamada a verifyToken() y, por lo tanto, agrega latencia a cualquier extremo que la use. Por este motivo, te recomendamos que habilites la protección contra la repetición solo en extremos particularmente sensibles.

Para usar la protección contra repeticiones, haz lo siguiente:

  1. En Cloud Console, otorga el rol “Verificador de tokens de Verificación de aplicaciones de Firebase” a la cuenta de servicio que se usa para verificar tokens.

    • Si inicializaste el SDK de Admin con las credenciales de la cuenta de servicio del SDK de Admin que descargaste de Firebase console, el rol requerido ya está otorgado.
    • Si usas Cloud Functions de primera generación con la configuración predeterminada del SDK de Admin, otorga el rol a la cuenta de servicio predeterminada de App Engine. Consulta Cambia los permisos de la cuenta de servicio.
    • Si usas Cloud Functions de segunda generación con la configuración predeterminada del SDK de Admin, otorga el rol a la cuenta de servicio predeterminada de Compute.
  2. Luego, para consumir un token, pasa { consume: true } al método verifyToken() y examina el objeto Resultado. Si la propiedad alreadyConsumed es true, rechaza la solicitud o aplica algún tipo de medida correctiva, como requerir que el emisor pase otras verificaciones.

    Por ejemplo:

    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.
    

    Esto verifica el token y luego lo marca como consumido. Las invocaciones futuras de verifyToken(appCheckToken, { consume: true }) en el mismo token establecerán alreadyConsumed en true (ten en cuenta que verifyToken() no rechaza un token consumido ni verifica si se consumió en caso de que no se configure consume).

Si habilitas esta función para un extremo en particular, también debes actualizar el código de cliente de la app para adquirir tokens consumibles de uso limitado y utilizarlos con el extremo. Consulta la documentación del cliente para plataformas de Apple, Android y la Web.