๐ก๏ธ Runbook: Enable Firebase App Check
- Severity: ๐ก Hardening (no active incident)
- Why: App Check attests that calls to Firestore, Storage, and the callable functions come from your genuine apps, not a scraped API key + curl. Today none of the clients enroll App Check and no callable enforces it, so the Firestore/Storage rules are the only barrier against an attacker holding a (public, by-design) Firebase web API key.
- Blocked on a human: App Check needs provider keys generated in consoles (reCAPTCHA, Apple, Google Play). Do those first, then the code is one line per surface.
[!WARNING] Never turn on
enforceAppCheckbefore clients are sending tokens. Enabling enforcement first will reject every real user. Follow the phased order below: enroll clients โ watch the App Check "unverified" metrics in the console for a full release cycle โ only then flip enforcement on.
1. Generate the provider keys (console work)โ
| Surface | Provider | Where |
|---|---|---|
| Web (app.acesense.io, admin) | reCAPTCHA Enterprise or reCAPTCHA v3 | Firebase Console โ App Check โ register each web app; add domains app.acesense.io, admin.acesense.io, localhost |
| iOS | App Attest (prod) / DeviceCheck | Firebase Console โ App Check โ iOS app; no extra key, uses the app's entitlement |
| Android | Play Integrity | Firebase Console โ App Check โ Android app |
The reCAPTCHA site key is public (safe to embed). Keep the secret key in the console only.
2. Enroll the clientsโ
Web โ acesense-admin: โ
ALREADY WIRED.
It calls initializeAppCheck with a ReCaptchaV3Provider, gated behind
VITE_APPCHECK_SITE_KEY (and skipped in dev). It is inert until you set
that env var to your reCAPTCHA v3 site key and rebuild/redeploy โ no code
change needed. To turn it on: set VITE_APPCHECK_SITE_KEY in the admin deploy
env, pnpm build, firebase deploy --only hosting:admin.
acesense-landing no longer writes to Firestore from the browser, so it has no
App Check enrollment code today.
Flutter โ acesense-frontend: โ
ALREADY WIRED (web + iOS verified).
firebase_app_check is added and lib/main.dart calls
FirebaseAppCheck.instance.activate(...) (web=ReCaptchaV3, iOS=App Attest,
Android=Play Integrity), gated behind --dart-define=ENABLE_APP_CHECK=true. The
iOS native integration is confirmed (flutter build ios --no-codesign builds
Runner.app with the pod). To turn it on, build with:
flutter build <target> --dart-define=ENABLE_APP_CHECK=true \
--dart-define=APPCHECK_RECAPTCHA_KEY=<web reCAPTCHA v3 key>
iOS/Android need no key (App Attest / Play Integrity). Off until then.
3. Watch (do not enforce yet)โ
Ship steps 1โ2. In Firebase Console โ App Check, watch the verified vs unverified request ratio per product (Firestore, Storage, Functions) across a full release cycle (web is instant; iOS/Android lag until users update). Wait until verified โ 100% of legitimate traffic.
4. Enforceโ
- Firestore / Storage: flip "Enforce" per product in the console.
- Callables โ
acesense-auth-function: spread...appCheckOptions()into theonCall({ region: REGION, ... })options (already wired onrequestUploadPathand coach callables). Set function envENFORCE_APP_CHECK=trueto enforce; default is off. Start with the high-value, app-only callables (requestUploadPath,createCoachLink,addCoachNote, theadmin*callables). Do NOT enforce on the public REST surface (apiServer,mcpServer) โ those are authenticated by API keys, not App Check, and have no app to attest.
Callable enforcement:
requestUploadPathand all coach callables spreadappCheckOptions()fromshared/app-check.tsโ enforcement activates only whenENFORCE_APP_CHECK=trueon the function env; default is off.
Rollbackโ
Enforcement is a console toggle (off in seconds) and the callable option is a
one-line revert + redeploy. The client activate() call is harmless to leave in
place even when enforcement is off.
Wave 2 โ client + callable wiring (2026-07)โ
- Flutter client:
lib/main.dartcallsFirebaseAppCheck.instance.activate(...)viaappCheckOptions()fromlib/config/app_check_flags.dart. Gated behind--dart-define=ENABLE_APP_CHECK=true; web additionally requires--dart-define=APPCHECK_RECAPTCHA_KEY=<reCAPTCHA v3 site key>. - Callables:
acesense-auth-functionspreadsappCheckOptions()fromshared/app-check.tsintoonCalloptions. Set function envENFORCE_APP_CHECK=trueto enforce; default is off. Do not enforce on public REST surfaces (apiServer,mcpServer).