Mediante App Check, 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 App Check 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 App Check válido con cada solicitud, como se describe en esta página.
Verificación de tokens
Agrega lógica a los extremos de la API para verificar los tokens de App Check en tu backend que haga lo siguiente:
Verificar que cada solicitud incluya un token de App Check
Verificar el token de App Check con el SDK de Admin
Si la verificación se realiza correctamente, el SDK de Admin muestra el token de App Check 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:
- 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
- Verifica la firma del token de la Verificación de aplicaciones para asegurarte de que sea legítima.
- Asegúrate de que el encabezado del token use el algoritmo RS256.
- Asegúrate de que el encabezado del token tenga el tipo JWT.
- Asegúrate de que la Verificación de aplicaciones de Firebase emita el token en tu proyecto.
- Asegúrate de que el token no haya vencido.
- Asegúrate de que el público del token coincida con tu proyecto.
- 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:
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.
Luego, para consumir un token, pasa
{ consume: true }
al métodoverifyToken()
y examina el objeto Resultado. Si la propiedadalreadyConsumed
estrue
, 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ánalreadyConsumed
entrue
(ten en cuenta queverifyToken()
no rechaza un token consumido ni verifica si se consumió en caso de que no se configureconsume
).
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.