자체 호스팅 백엔드와 같은 앱의 Firebase 외의 리소스를 App Check로 보호할 수 있습니다. 이렇게 하려면 다음 두 가지 작업을 모두 수행해야 합니다.
- iOS+, Android, 웹 페이지에 설명된 대로 각 요청에 따라 App Check 토큰을 백엔드로 보내도록 앱 클라이언트를 수정합니다.
- 이 페이지에 설명된 대로 모든 요청에 유효한 App Check 토큰을 요구하도록 백엔드를 수정합니다.
토큰 확인
백엔드에서 App Check 토큰을 확인하려면 다음을 수행하는 로직을 API 엔드포인트에 추가합니다.
각 요청에 App Check 토큰이 포함되어 있는지 확인하세요.
Admin SDK를 사용하여 App Check 토큰을 확인합니다.
확인에 성공하면 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
Go용 Admin SDK를 아직 설치하지 않았다면 설치합니다.
그런 다음 API 엔드포인트 핸들러에서 appcheck.Client.VerifyToken()
을 호출하고, 실패하면 요청을 거부합니다. 다음 예시에서 래퍼 함수가 엔드포인트 핸들러에 이 로직을 추가합니다.
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.io에 있는 것과 같은 범용 JWT 라이브러리를 사용하여 앱 체크 토큰을 확인할 수 있습니다.
토큰 확인 로직은 다음 단계를 완료해야 합니다.
- 앱 체크 JWKS 엔드포인트에서 Firebase 앱 체크 공개 JSON 웹 키(JWK) 집합을 가져옵니다.
https://firebaseappcheck.googleapis.com/v1/jwks
- 앱 체크 토큰의 서명이 합법적인지 확인합니다.
- 토큰의 헤더가 알고리즘 RS256을 사용하는지 확인합니다.
- 토큰의 헤더에 JWT 유형이 있는지 확인합니다.
- 토큰이 프로젝트의 Firebase 앱 체크에서 발급되었는지 확인합니다.
- 토큰이 만료되지 않았는지 확인합니다.
- 토큰의 잠재고객이 프로젝트와 일치하는지 확인합니다.
- 선택사항: 토큰의 제목이 앱의 앱 ID와 일치하는지 확인합니다.
JWT 라이브러리의 기능은 다를 수 있습니다. 선택한 라이브러리에서 처리하지 않는 모든 단계를 수동으로 완료해야 합니다.
다음 예시에서는 jwt
gem을 Rack 미들웨어 레이어로 사용하여 Ruby에서 필요한 단계를 수행합니다.
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
재생 보호(베타)
재생 공격으로부터 엔드포인트를 보호하려면 앱 체크 토큰을 한 번만 사용할 수 있도록 확인한 후에 이를 소비하면 됩니다.
재생 보호 기능을 사용하면 verifyToken()
호출에 네트워크 왕복이 추가되므로 이 기능을 사용하는 모든 엔드포인트에 지연 시간이 추가됩니다. 따라서 특히 민감한 엔드포인트에만 재생 보호를 사용 설정하는 것이 좋습니다.
재생 보호를 사용하려면 다음을 수행합니다.
Cloud 콘솔에서 토큰을 확인하는 데 사용되는 서비스 계정에 'Firebase 앱 체크 토큰 확인자' 역할을 부여합니다.
- Firebase Console에서 다운로드한 Admin SDK 서비스 계정 사용자 인증 정보로 Admin SDK를 초기화한 경우 필수 역할이 이미 부여된 것입니다.
- 1세대 Cloud Functions를 기본 Admin SDK 구성과 함께 사용하는 경우 App Engine 기본 서비스 계정에 역할을 부여합니다. 서비스 계정 권한 변경을 참조하세요.
- 2세대 Cloud Functions를 기본 Admin SDK 구성과 함께 사용하는 경우 기본 컴퓨팅 서비스 계정에 역할을 부여합니다.
그런 다음 토큰을 사용하려면
{ 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, 웹에 대한 클라이언트 측 문서를 참조하세요.