คุณสามารถใช้ App Check เพื่อปกป้องทรัพยากรแบ็กเอนด์ที่กำหนดเองซึ่งไม่ใช่ของ Google สำหรับแอป เช่น แบ็กเอนด์ที่โฮสต์เอง โดยคุณจะต้องดำเนินการทั้ง 2 อย่างต่อไปนี้
- แก้ไขไคลเอ็นต์แอปเพื่อส่งโทเค็น App Check พร้อมกับคำขอแต่ละรายการไปยังแบ็กเอนด์ตามที่อธิบายไว้ในหน้าสำหรับ iOS+, Android, เว็บ, Flutter, Unity หรือ C++
- แก้ไขแบ็กเอนด์ให้ต้องใช้โทเค็น App Check ที่ถูกต้องกับทุกคำขอตามที่อธิบายไว้ในหน้านี้
การยืนยันโทเค็น
หากต้องการยืนยันโทเค็น App Check ในแบ็กเอนด์ ให้เพิ่มตรรกะลงในปลายทาง API ที่ทำสิ่งต่อไปนี้
ตรวจสอบว่าคำขอแต่ละรายการมีโทเค็น App Check
ยืนยันโทเค็น App Check โดยใช้ Admin SDK
หากการยืนยันสำเร็จ Admin 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 Admin SDK ให้ติดตั้ง
จากนั้นในเครื่องจัดการปลายทาง API ให้เรียกใช้ app_check.verify_token() และปฏิเสธคำขอหากไม่สำเร็จ ในตัวอย่างต่อไปนี้ ฟังก์ชันที่ตกแต่งด้วย @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.
...
Go
หากยังไม่ได้ติดตั้ง Admin SDK สำหรับ Go ให้ติดตั้ง
จากนั้นในเครื่องจัดการปลายทาง API ให้เรียกใช้ appcheck.Client.VerifyToken() และปฏิเสธคำขอหากไม่สำเร็จ ในตัวอย่างต่อไปนี้ ฟังก์ชัน Wrapper จะเพิ่มตรรกะนี้ลงในเครื่องจัดการปลายทาง
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.
}
อื่นๆ
หากแบ็กเอนด์เขียนด้วยภาษาอื่น คุณสามารถใช้ไลบรารี JWT อเนกประสงค์ เช่น ไลบรารีที่พบใน jwt.io เพื่อยืนยันโทเค็น App Check
ตรรกะการยืนยันโทเค็นต้องทำตามขั้นตอนต่อไปนี้ให้เสร็จสมบูรณ์
- รับชุดคีย์เว็บ JSON (JWK) สาธารณะของ Firebase App Check จากปลายทาง JWKS ของ App Check ดังนี้
https://firebaseappcheck.googleapis.com/v1/jwks - ยืนยันลายเซ็นของโทเค็น App Check เพื่อให้แน่ใจว่าถูกต้องตามกฎหมาย
- ตรวจสอบว่าส่วนหัวของโทเค็นใช้อัลกอริทึม RS256
- ตรวจสอบว่าส่วนหัวของโทเค็นมีประเภท JWT
- ตรวจสอบว่าโทเค็นออกโดย Firebase App Check ภายใต้โปรเจ็กต์ของคุณ
- ตรวจสอบว่าโทเค็นยังไม่หมดอายุ
- ตรวจสอบว่ากลุ่มเป้าหมายของโทเค็นตรงกับโปรเจ็กต์ของคุณ
- ไม่บังคับ: ตรวจสอบว่าเรื่องของโทเค็นตรงกับรหัสแอปของแอป
ความสามารถของไลบรารี JWT อาจแตกต่างกัน โปรดทำตามขั้นตอนที่ไลบรารีที่คุณเลือกไม่ได้จัดการด้วยตนเองให้เสร็จสมบูรณ์
ตัวอย่างต่อไปนี้จะทำตามขั้นตอนที่จำเป็นใน Ruby โดยใช้ jwt
gem เป็นเลเยอร์มิดเดิลแวร์ของ 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 หลังจากยืนยันแล้วเพื่อให้ใช้ได้เพียงครั้งเดียว
การใช้การป้องกันการเล่นซ้ำจะเพิ่มการส่งข้อมูลไปกลับในเครือข่ายไปยังการเรียกใช้การยืนยันโทเค็น จึงเพิ่มเวลาในการตอบสนองให้กับปลายทางที่ใช้การป้องกันดังกล่าว ด้วยเหตุนี้ เราขอแนะนำให้คุณเปิดใช้การป้องกันการเล่นซ้ำเฉพาะในปลายทางที่มีความละเอียดอ่อนเป็นพิเศษเท่านั้น
หากต้องการใช้การป้องกันการเล่นซ้ำ ให้ทำดังนี้
ในคอนโซล Cloud ให้มอบบทบาท "Firebase App Check Token Verifier" ให้กับบัญชีบริการที่ใช้ในการยืนยันโทเค็น
- หากคุณเริ่มต้น Admin SDK ด้วยข้อมูลเข้าสู่ระบบของบัญชีบริการ Admin SDK ที่ดาวน์โหลดจากคอนโซล Firebase ระบบจะให้สิทธิ์บทบาทที่จำเป็นอยู่แล้ว
- หากคุณใช้ Cloud Functions รุ่นที่ 1 กับการกำหนดค่า Admin SDK เริ่มต้น ให้มอบบทบาทให้กับบัญชีบริการเริ่มต้นของ App Engine โปรดดูหัวข้อการเปลี่ยนสิทธิ์ของบัญชีบริการ
- หากคุณใช้ Cloud Functions รุ่นที่ 2 กับการกำหนดค่า Admin SDK เริ่มต้น ให้มอบบทบาทให้กับบัญชีบริการ Compute เริ่มต้น
หากต้องการใช้โทเค็น ให้ส่ง
{ consume: true }(Node.js) หรือconsume=True(Python) ไปยังการเรียกใช้การยืนยันโทเค็น แล้วตรวจสอบออบเจ็กต์ผลลัพธ์ หากใช้โทเค็นไปแล้ว ให้ปฏิเสธคำขอหรือดำเนินการแก้ไข เช่น กำหนดให้ผู้เรียกใช้ต้องผ่านการตรวจสอบอื่นๆ ดังนี้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)
เมื่อเปิดใช้ฟีเจอร์นี้สำหรับปลายทางหนึ่งๆ คุณต้องอัปเดตโค้ดไคลเอ็นต์ของแอปเพื่อรับโทเค็นที่จำกัดการใช้ซึ่งใช้ได้สำหรับใช้กับปลายทางด้วย ดูเอกสารประกอบฝั่งไคลเอ็นต์สำหรับแพลตฟอร์ม Apple, Android และเว็บ