Why increasing Firebase App Check TTL won’t cut reCAPTCHA costs

Summary

A production environment experienced unexpectedly high billing costs due to reCAPTCHA Enterprise assessments within a Firebase App Check implementation. The engineering team attempted to mitigate costs by increasing the Token Time to Live (TTL) from 1 day to 7 days, assuming this would reduce the frequency of assessment requests. However, the billing metrics remained unchanged, leading to confusion regarding the lifecycle of an App Check token and the triggering mechanism of reCAPTCHA assessments.

Root Cause

The failure of the TTL optimization stems from a fundamental misunderstanding of the App Check lifecycle versus the reCAPTCHA assessment lifecycle.

  • App Check Token Lifecycle: Increasing TTL only extends how long a locally cached Firebase App Check token remains valid for authenticating with Firebase services (like Firestore or Cloud Functions).
  • Assessment Trigger Mechanism: A reCAPTCHA assessment is triggered whenever the App Check SDK needs to refresh or obtain a new token.
  • The Disconnect: In many web implementations, if the application logic or the SDK’s internal state forces a re-initialization or if the user’s session state doesn’t persist the token correctly in local storage, the SDK will request a new token regardless of the TTL settings.
  • Client-Side Behavior: If the Flutter Web app is reloaded or the user navigates in a way that clears the in-memory state without persisting the token to IndexedDB or LocalStorage, the app will request a new assessment every single time the page loads.

Why This Happens in Real Systems

In complex production environments, several factors prevent TTL settings from behaving as theoretically expected:

  • State Volatility: Single Page Applications (SPAs) often lose in-memory state on hard refreshes. If the App Check token is not explicitly persisted to a persistent browser storage, the TTL becomes irrelevant because the client “forgets” it has a token.
  • Service-Side Validation vs. Client-Side Generation: TTL governs how long a token is trusted by the backend, but it does not dictate how often the client decides it needs a new one.
  • Aggressive Re-authentication: Security middleware or custom wrappers around Firebase services may be programmed to call getToken(forceRefresh: true), which bypasses the TTL entirely and forces a new reCAPTCHA assessment.

Real-World Impact

  • Exponential Cost Scaling: As user traffic grows, the number of unnecessary assessments scales linearly, leading to unpredictable cloud billing spikes.
  • Latency Penalties: Every unnecessary reCAPTCHA assessment introduces a network round-trip, increasing the Time to Interactive (TTI) for the end user.
  • Resource Exhaustion: High assessment rates can hit API quotas, potentially causing legitimate users to be blocked from accessing protected backend resources.

Example or Code

// WRONG: Forcing a refresh every time, ignoring TTL
const token = await getToken(appCheck, true); 

// RIGHT: Allow the SDK to use the cached token if it hasn't expired
const token = await getToken(appCheck);

// CORRECT: Ensure persistence is enabled in the App Check configuration
// (Logic varies by platform, but for Web, ensure storage is not cleared)

How Senior Engineers Fix It

Senior engineers look beyond simple configuration toggles and audit the entire request flow:

  • Audit Token Retrieval Patterns: Search the codebase for any instances where forceRefresh is set to true. Ensure the SDK is allowed to use its internal cache.
  • Implement Persistent Storage: Ensure the App Check provider is configured to use IndexedDB or another persistent mechanism so that a page refresh does not trigger a new assessment.
  • Observability & Tracing: Use Cloud Logging to correlate App Check Token Issuance events with reCAPTCHA Assessment events. This identifies if the assessments are coming from legitimate token refreshes or redundant calls.
  • Client-Side Throttling: Implement logic to ensure that the application doesn’t attempt to re-initialize the Firebase SDK multiple times during the bootstrap process.

Why Juniors Miss It

  • Focus on Configuration over Logic: Juniors often assume that changing a “setting” in the Firebase Console is a global magic wand that overrides application behavior.
  • Lack of Lifecycle Awareness: They often treat the App Check token as a “session” rather than a cryptographic artifact with a specific expiration and retrieval flow.
  • Ignoring Side Effects: They may not realize that a simple window.location.reload() in a web app effectively resets the entire security state, making a 7-day TTL mathematically useless if the storage is wiped.

Leave a Comment