Vous pouvez utiliser App Check pour protéger les ressources de backend personnalisées non Google de votre application, comme votre propre backend auto-hébergé. Pour ce faire, vous devez effectuer les deux opérations suivantes :
- Modifiez votre client d'application pour envoyer un jeton App Check avec chaque requête à votre backend, comme décrit sur les pages pour iOS+, Android, Web, Flutter, Unity ou C++.
- Modifiez votre backend pour exiger un jeton App Check valide pour chaque requête, comme décrit sur cette page.
Validation des jetons
Pour valider les jetons App Check sur votre backend, ajoutez une logique à vos points de terminaison d'API qui effectue les opérations suivantes :
Vérifiez que chaque requête inclut un jeton App Check.
Vérifiez le jeton App Check à l'aide du SDK Admin.
Si la validation réussit, le SDK Admin renvoie le jeton App Check décodé. Une validation réussie indique que le jeton provient d'une application appartenant à votre projet Firebase.
Rejeter toute requête qui échoue à l'un des contrôles. Exemple :
Node.js
Si vous n'avez pas encore installé le SDK Admin Node.js, faites-le.
Ensuite, en utilisant le middleware Express.js comme exemple :
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 vous n'avez pas encore installé le SDK Admin Python, faites-le.
Ensuite, dans vos gestionnaires de points de terminaison d'API, appelez app_check.verify_token() et refusez la requête si elle échoue. Dans l'exemple suivant, une fonction décorée avec @before_request effectue cette tâche pour toutes les requêtes :
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.
...
Accéder
Si vous n'avez pas encore installé le SDK Admin pour Go, faites-le.
Ensuite, dans vos gestionnaires de points de terminaison d'API, appelez appcheck.Client.VerifyToken() et rejetez la requête en cas d'échec. Dans l'exemple suivant, une fonction wrapper ajoute cette logique aux gestionnaires de points de terminaison :
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.
}
Java
Si vous n'avez pas encore installé le SDK Admin pour Java (version 9.12.0 ou ultérieure), faites-le.
Ensuite, dans vos gestionnaires de points de terminaison d'API ou vos filtres de requêtes, appelez FirebaseAppCheck.getInstance().verifyToken() (ou verifyTokenAsync()) et refusez la requête en cas d'échec. L'exemple suivant utilise un filtre Spring Boot pour valider le jeton des requêtes entrantes :
import com.google.firebase.FirebaseApp;
import com.google.firebase.appcheck.FirebaseAppCheck;
import com.google.firebase.appcheck.FirebaseAppCheckException;
import com.google.firebase.appcheck.VerifyAppCheckTokenResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.filter.OncePerRequestFilter;
@SpringBootApplication
@RestController
public class Application {
public static void main(String[] args) {
FirebaseApp.initializeApp();
SpringApplication.run(Application.class, args);
}
@GetMapping("/yourApiEndpoint")
public String yourApiEndpoint() {
// Handle request.
return "Success";
}
@Component
public static class AppCheckFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String appCheckToken = request.getHeader("X-Firebase-AppCheck");
if (appCheckToken == null || appCheckToken.isEmpty()) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
return;
}
try {
VerifyAppCheckTokenResponse appCheckResponse =
FirebaseAppCheck.getInstance().verifyToken(appCheckToken);
// If verifyToken() succeeds, continue with the filter chain.
filterChain.doFilter(request, response);
} catch (FirebaseAppCheckException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
}
}
}
}
Autre
Si votre backend est écrit dans un autre langage, vous pouvez utiliser une bibliothèque JWT à usage général, comme celle disponible sur jwt.io, pour valider les jetons App Check.
Votre logique de validation des jetons doit suivre les étapes suivantes :
- Obtenez le jeu de clés Web JSON (JWK) public Firebase App Check à partir du point de terminaison JWKS App Check :
https://firebaseappcheck.googleapis.com/v1/jwks - Vérifiez la signature du jeton App Check pour vous assurer qu'elle est légitime.
- Assurez-vous que l'en-tête du jeton utilise l'algorithme RS256.
- Assurez-vous que l'en-tête du jeton est de type JWT.
- Assurez-vous que le jeton est émis par Firebase App Check dans votre projet.
- Assurez-vous que le jeton n'a pas expiré.
- Assurez-vous que l'audience du jeton correspond à votre projet.
- Facultatif : Vérifiez que l'objet du jeton correspond à l'ID de votre application.
Les fonctionnalités des bibliothèques JWT peuvent varier. Veillez à effectuer manuellement toutes les étapes qui ne sont pas gérées par la bibliothèque que vous choisissez.
L'exemple suivant effectue les étapes nécessaires en Ruby à l'aide du gem jwt en tant que couche 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
Protection contre les attaques de répétition (bêta)
Pour protéger un point de terminaison contre les attaques par rejeu, vous pouvez utiliser le jeton App Check après l'avoir validé, afin qu'il ne puisse être utilisé qu'une seule fois.
L'utilisation de la protection contre les attaques par rejeu ajoute un aller-retour réseau à l'appel de validation du jeton, ce qui augmente la latence de tout point de terminaison qui l'utilise. Pour cette raison, nous vous recommandons de n'activer la protection contre la relecture que sur les points de terminaison particulièrement sensibles.
Pour utiliser la protection contre la relecture, procédez comme suit :
Dans la console Cloud, attribuez le rôle "Vérificateur de jetons Firebase App Check" au compte de service utilisé pour vérifier les jetons.
- Si vous avez initialisé le SDK Admin avec les identifiants du compte de service du SDK Admin que vous avez téléchargés depuis la console Firebase, le rôle requis est déjà accordé.
- Si vous utilisez Cloud Functions (1re génération) avec la configuration par défaut du SDK Admin, attribuez le rôle au compte de service App Engine par défaut. Consultez Modifier les autorisations des comptes de service.
- Si vous utilisez Cloud Functions (2e génération) avec la configuration par défaut du SDK Admin, attribuez le rôle au compte de service Compute par défaut.
Pour utiliser un jeton, transmettez
{ consume: true }(Node.js),consume=True(Python) ouVerifyAppCheckTokenOptionsavecsetConsume(true)(Java) à l'appel de validation du jeton, ou appelezVerifyOneTimeToken()(Go), puis examinez l'objet de résultat. Si le jeton a déjà été utilisé, refusez la requête ou prenez des mesures correctives, par exemple en demandant à l'appelant de passer d'autres contrôles :Node.js
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.Cela permet de vérifier le jeton, puis de le marquer comme consommé. Les appels futurs de
verifyToken(appCheckToken, { consume: true })sur le même jeton définissentalreadyConsumedsurtrue. (Notez queverifyToken()ne rejette pas un jeton consommé et ne vérifie même pas s'il est consommé siconsumen'est pas défini.)Python
app_check_claims = app_check.verify_token(app_check_token, consume=True) if app_check_claims.get("already_consumed"): return "App Check token already consumed", 401 # If verify_token() succeeds and already_consumed is not set, okay to continue.Cela permet de vérifier le jeton, puis de le marquer comme consommé. Les appels futurs de
app_check.verify_token(app_check_token, consume=True)sur le même jeton définissentalready_consumedsurTrue. (Notez queapp_check.verify_token()ne rejette pas un jeton consommé et ne vérifie même pas s'il est consommé siconsumen'est pas défini.)Accéder
decodedToken, err := appCheck.VerifyOneTimeToken(r.Context(), appCheckToken[0]) if err != nil || (decodedToken.AlreadyConsumed != nil && *decodedToken.AlreadyConsumed) { w.WriteHeader(http.StatusUnauthorized) w.Write([]byte("Unauthorized.")) return } // If VerifyOneTimeToken() succeeds and AlreadyConsumed is not true, okay to // continue with the provided handler.Cela permet de vérifier le jeton, puis de le marquer comme consommé. Les appels ultérieurs de
appCheck.VerifyOneTimeToken()sur le même jeton définissentAlreadyConsumedsurtrue. (Notez queappCheck.VerifyToken()ne rejette pas un jeton consommé et ne vérifie même pas s'il est consommé.)Java
VerifyAppCheckTokenOptions options = VerifyAppCheckTokenOptions.builder() .setConsume(true) .build(); VerifyAppCheckTokenResponse appCheckResponse = FirebaseAppCheck.getInstance().verifyToken(appCheckToken, options); if (appCheckResponse.isAlreadyConsumed().orElse(false)) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); return; } // If verifyToken() succeeds and isAlreadyConsumed() is not true, continue // with the filter chain.Cela permet de vérifier le jeton, puis de le marquer comme consommé. Les appels futurs de
verifyToken(appCheckToken, options)avecsetConsume(true)sur le même jeton renvoientOptional.of(true)à partir deisAlreadyConsumed(). (Notez queverifyToken()ne rejette pas un jeton consommé et ne vérifie même pas s'il est consommé sisetConsume(true)n'est pas défini.)
Lorsque vous activez cette fonctionnalité pour un point de terminaison particulier, vous devez également mettre à jour le code client de votre application pour acquérir des jetons à usage limité consommables à utiliser avec le point de terminaison. Consultez la documentation côté client pour les plates-formes Apple, Android et le Web.