คุณสามารถใช้ 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 Web Key (JWK) สาธารณะของ App Check ใน Firebase จากปลายทาง JWKS ของ App
Check ดังนี้
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 หลังจากยืนยันแล้วเพื่อให้ใช้ได้เพียงครั้งเดียว
การใช้การป้องกันการเล่นซ้ำจะเพิ่มการรับส่งข้อมูลในเครือข่ายไปยังverifyToken()
การโทร และทำให้เกิดเวลาในการตอบสนองที่ปลายทางใดก็ตามที่ใช้การป้องกันนี้ ด้วยเหตุนี้ เราจึงขอแนะนำให้คุณเปิดใช้การป้องกันการเล่นซ้ำเฉพาะในอุปกรณ์ปลายทางที่มีความละเอียดอ่อนเป็นพิเศษ
หากต้องการใช้การป้องกันการเล่นซ้ำ ให้ทำดังนี้
ในCloud Console ให้มอบบทบาท "เครื่องมือยืนยันโทเค็น App Check ของ Firebase" ให้กับบัญชีบริการ ที่ใช้เพื่อยืนยันโทเค็น
- หากคุณเริ่มต้น Admin SDK ด้วยข้อมูลเข้าสู่ระบบของบัญชีบริการ Admin SDK ที่ดาวน์โหลดจากคอนโซล Firebase ระบบจะให้บทบาทที่จำเป็นแก่คุณแล้ว
- หากคุณใช้ Cloud Functions รุ่นที่ 1 ที่มีการกำหนดค่า Admin SDK เริ่มต้น ให้มอบบทบาทให้กับบัญชีบริการเริ่มต้นของ App Engine ดูการเปลี่ยนสิทธิ์ของบัญชีบริการ
- หากคุณใช้ Cloud Functions รุ่นที่ 2 ที่มีการกำหนดค่า Admin SDK เริ่มต้น ให้มอบบทบาทให้กับบัญชีบริการ Compute เริ่มต้น
จากนั้นหากต้องการใช้โทเค็น ให้ส่ง
{ consume: true }
ไปยังเมธอดverifyToken()
และตรวจสอบออบเจ็กต์ผลลัพธ์ หากพร็อพเพอร์ตี้alreadyConsumed
เป็นtrue
ให้ปฏิเสธคำขอหรือดำเนินการแก้ไขบางอย่าง เช่น กำหนดให้ผู้เรียกผ่านการตรวจสอบอื่นๆเช่น
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
)
เมื่อเปิดใช้ฟีเจอร์นี้สำหรับอุปกรณ์ปลายทางหนึ่งๆ คุณต้องอัปเดต โค้ดไคลเอ็นต์ของแอปเพื่อรับโทเค็นแบบจำกัดการใช้งานที่ใช้ได้สำหรับ อุปกรณ์ปลายทางด้วย ดูเอกสารฝั่งไคลเอ็นต์สำหรับ แพลตฟอร์ม Apple Android และ เว็บ