자체 호스팅 백엔드와 같은 앱의 Firebase 외의 리소스를 앱 체크로 보호할 수 있습니다. 이렇게 하려면 다음 두 가지 작업을 모두 수행해야 합니다.
- iOS+, Android, 웹 페이지에 설명된 대로 각 요청에 따라 앱 체크 토큰을 백엔드로 보내도록 앱 클라이언트를 수정합니다.
- 이 페이지에 설명된 대로 모든 요청에 유효한 앱 체크 토큰을 요구하도록 백엔드를 수정합니다.
토큰 확인
백엔드에서 앱 체크 토큰을 확인하려면 다음을 수행하는 로직을 API 엔드포인트에 추가합니다.
각 요청에 앱 체크 토큰이 포함되어 있는지 확인하세요.
Admin SDK를 사용하여 앱 체크 토큰을 확인합니다.
확인에 성공하면 Admin SDK가 디코딩된 앱 체크 토큰을 반환합니다. 인증에 성공하면 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, 웹에 대한 클라이언트 측 문서를 참조하세요.