אתם יכולים להשתמש ב-App Check כדי להגן על משאבי קצה עורפי מותאמים אישית של האפליקציה שלא שייכים ל-Google, כמו קצה עורפי באירוח עצמי. כדי לעשות את זה, צריך לבצע את שתי הפעולות הבאות:
- משנים את הלקוח של האפליקציה כך שישלח אסימון App Check יחד עם כל בקשה לקצה העורפי, כפי שמתואר בדפים של iOS+, Android, אינטרנט, Flutter, Unity או C++.
- משנים את הקצה העורפי כך שידרוש אסימון App Check תקין בכל בקשה, כמו שמתואר בדף הזה.
אימות טוקן
כדי לאמת טוקנים של App Check בקצה העורפי, מוסיפים לוגיקה לנקודות הקצה ל-API שמבצעת את הפעולות הבאות:
מוודאים שכל בקשה כוללת אסימון App Check.
מאמתים את הטוקן של App Check באמצעות SDK לאדמינים.
אם האימות מצליח, ה-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
אם עדיין לא התקנתם את Python SDK לאדמינים, התקינו אותו.
לאחר מכן, ב-handlers של נקודת הקצה ל-API, קוראים ל-app_check.verify_token() ודוחים את הבקשה אם היא נכשלת. בדוגמה הבאה, פונקציה עם ה-decorator @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.
...
המשך
אם עדיין לא התקנתם את SDK לאדמינים ל-Go, התקינו אותו.
לאחר מכן, ב-handlers של נקודות הקצה ל-API, צריך להפעיל את appcheck.Client.VerifyToken() ולדחות את הבקשה אם היא נכשלת. בדוגמה הבאה, פונקציית wrapper מוסיפה את הלוגיקה הזו ל-endpoint handlers:
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
אם עדיין לא התקנתם את SDK לאדמינים ל-Java (גרסה 9.12.0 ואילך), התקינו אותו.
לאחר מכן, ב-handlers של נקודת הקצה ל-API או במסנני הבקשות, קוראים ל-FirebaseAppCheck.getInstance().verifyToken() (או ל-verifyTokenAsync()) ודוחים את הבקשה אם היא נכשלת. בדוגמה הבאה נעשה שימוש במסנן Spring Boot כדי לאמת את הטוקן של בקשות נכנסות:
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");
}
}
}
}
אחר
אם הקצה העורפי שלכם כתוב בשפה אחרת, אתם יכולים להשתמש בספריית JWT למטרות כלליות, כמו זו שנמצאת בכתובת jwt.io, כדי לאמת אסימונים של App Check.
לוגיקת אימות הטוקן צריכה להשלים את השלבים הבאים:
- מקבלים את קבוצת מפתחות האינטרנט הציבוריים מסוג JSON (JWK) של Firebase App Check מנקודת הקצה של App Check JWKS:
https://firebaseappcheck.googleapis.com/v1/jwks - מאמתים את החתימה של האסימון של App Check כדי לוודא שהיא תקינה.
- מוודאים שהכותרת של האסימון משתמשת באלגוריתם RS256.
- מוודאים שהכותרת של האסימון היא מסוג JWT.
- מוודאים שהאסימון הונפק על ידי Firebase App Check בפרויקט שלכם.
- מוודאים שהתוקף של הטוקן לא פג.
- מוודאים שהקהל של הטוקן תואם לפרויקט.
- אופציונלי: בודקים שהנושא של הטוקן תואם למזהה האפליקציה.
היכולות של ספריות JWT יכולות להיות שונות. הקפידו לבצע באופן ידני את כל השלבים שלא מטופלים על ידי הספרייה שבחרתם.
בדוגמה הבאה, השלבים הנדרשים מבוצעים ב-Ruby באמצעות ה-gem 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
הגנה מפני הפעלה מחדש (בטא)
כדי להגן על נקודת קצה מפני מתקפות מסוג Replay, אפשר להשתמש באסימון של App Check אחרי האימות שלו, כך שניתן יהיה להשתמש בו רק פעם אחת.
השימוש בהגנה מפני הפעלה חוזרת מוסיף זמן הלוך ושוב ברשת לקריאה לאימות הטוקן, ולכן מוסיף זמן אחזור לכל נקודת קצה שמשתמשת בה. לכן מומלץ להפעיל הגנה מפני מתקפות Replay רק בנקודות קצה (endpoints) רגישות במיוחד.
כדי להשתמש בהגנה מפני התקפות Replay:
במסוף Cloud, מקצים את התפקיד Firebase App Check Token Verifier לחשבון השירות שמשמש לאימות טוקנים.
- אם הפעלתם את SDK לאדמינים באמצעות פרטי הכניסה של חשבון השירות של SDK לאדמינים שהורדתם ממסוף Firebase, התפקיד הנדרש כבר הוקצה.
- אם אתם משתמשים בפונקציות של Cloud Functions מהדור הראשון עם הגדרת ברירת המחדל של Admin SDK, אתם צריכים להקצות את התפקיד לחשבון השירות שמשמש כברירת המחדל של App Engine. שינוי ההרשאות של חשבון השירות
- אם אתם משתמשים בפונקציות של Cloud Functions מהדור השני עם הגדרת ברירת המחדל של Admin SDK, הקצו את התפקיד לחשבון ברירת המחדל של שירות Compute.
כדי לצרוך אסימון, מעבירים את
{ consume: true }(Node.js),consume=True(Python) אוVerifyAppCheckTokenOptionsעםsetConsume(true)(Java) לקריאה לאימות האסימון, או קוראים ל-VerifyOneTimeToken()(Go), ואז בודקים את אובייקט התוצאה. אם כבר נעשה שימוש בטוקן, דוחים את הבקשה או מבצעים פעולה לתיקון, כמו לדרוש מהגורם ששלח את הבקשה לעבור בדיקות אחרות: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.הפעולה הזו מאמתת את האסימון ומסמנת אותו כמשומש. הפעלות עתידיות של
verifyToken(appCheckToken, { consume: true })באותו טוקן מגדירות אתalreadyConsumedלערךtrue. (שימו לב ש-verifyToken()לא דוחה אסימון שכבר נעשה בו שימוש, ואפילו לא בודק אם נעשה בו שימוש, אםconsumeלא מוגדר).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.הפעולה הזו מאמתת את הטוקן ומסמנת אותו כטוקן שכבר נעשה בו שימוש. הפעלות עתידיות של
app_check.verify_token(app_check_token, consume=True)באותו טוקן מגדירות אתalready_consumedלערךTrue. (שימו לב שהפונקציהapp_check.verify_token()לא דוחה אסימון שכבר נעשה בו שימוש, ואפילו לא בודקת אם נעשה בו שימוש אם לא הוגדרconsume).המשך
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.הפעולה הזו מאמתת את האסימון ומסמנת אותו כמשומש. הפעלות עתידיות של
appCheck.VerifyOneTimeToken()באותו טוקן מגדירות אתAlreadyConsumedלערךtrue. (שימו לב ש-appCheck.VerifyToken()לא דוחה אסימון שכבר נעשה בו שימוש, ואפילו לא בודקת אם נעשה בו שימוש).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.הפעולה הזו מאמתת את הטוקן ומסמנת אותו כטוקן שכבר נעשה בו שימוש. הפעלות עתידיות של
verifyToken(appCheckToken, options)עםsetConsume(true)באותו טוקן מחזירות אתOptional.of(true)מ-isAlreadyConsumed(). (שימו לב ש-verifyToken()לא דוחה אסימון שנעשה בו שימוש, ואפילו לא בודק אם נעשה בו שימוש אםsetConsume(true)לא מוגדר).
כשמפעילים את התכונה הזו בנקודת קצה מסוימת, צריך גם לעדכן את קוד הלקוח של האפליקציה כדי לקבל טוקנים לשימוש מוגבל שאפשר להשתמש בהם בנקודת הקצה. אפשר לעיין במסמכי התיעוד בצד הלקוח של פלטפורמות Apple, Android ואינטרנט.