DEV Community

Cover image for Firebase App Check: Protecting Your Backend from Abuse
Göktuğ Özdemir
Göktuğ Özdemir

Posted on

Firebase App Check: Protecting Your Backend from Abuse

I'm Berat Göktuğ Özdemir, a Senior Flutter Developer since 2018 and a Google Developer Expert for Firebase. I regularly speak at DevFest events about Flutter and Firebase.

In this post, I take a deep dive into Firebase App Check: why you need it, how attestation providers work across platforms, and a full Flutter implementation guide with debug provider setup, troubleshooting tips, and monitoring strategy. If you're building with Firebase and haven't set up App Check yet, this one's for you.

As developers, we spend a lot of time building amazing user experiences, but backend security is often an afterthought until it's too late. When using serverless technologies like Firebase, your API endpoints and databases are inherently exposed to the internet.

How can you ensure that the requests hitting your Firebase project are actually coming from your authentic app and not a malicious script, a bot, or a tampered client? This is where Firebase App Check comes in.

What is Firebase App Check?

In client applications, some Firebase configuration values must ship with the app. That often leads developers to assume their backend is safe as long as those values are “just config”. In reality, attackers can still script requests, abuse quotas, and target exposed endpoints unless additional protection is in place.

The issue is usually not that Firebase API keys are secret credentials that must be hidden at all costs. The real problem is that public client configuration alone cannot prove that a request truly comes from your untampered app. Anyone can extract these details and use them in a custom script, a bot, or a modified client to send unauthorized requests, manipulate your database, or exhaust your quotas. Do you know how you can block these unauthorized clients?

This is where Firebase App Check comes into play. It solves this exact problem by using a platform-specific app and device attestation to verify the client is valid. The diagram summarizes the behavior of the App Check between your app and Firebase services.

Diagram showing Firebase App Check acting as a security gate between an app and the backend: a green “User Request” is verified and allowed through to the backend servers, while a red “Bad Request” is rejected and blocked.

Green User (User Request): A real user of your app. App Check validates the request and delivers it securely to the backend.

Red User (Bad Request): A copied APK, emulator, bot, or an unauthorized request from a client. App Check blocks this request and prevents access to the backend.

The role of App Check: All requests must pass App Check validation. Only validated requests can access the backend.

Why should we use App Check?

There are a few reasons:

  • Significantly mitigates the risk of backend abuse from unauthorized clients.
  • Detects and prevents bot and automation attacks.
  • Protects real users and guards against the abuse of free and sensitive resources.
  • Only authorized apps with verified integrity can access your Firebase services.
  • App Check provides layered protection by working together with your security rules and authentication.

TL;DR: App Check is a powerful gatekeeper between your app and Firebase services.
App Check significantly raises the bar by allowing only requests with valid attestation to reach protected resources, but it should be treated as one layer in a broader security strategy.

How Does It Work?

Firebase App Check relies on platform-specific services called Attestation Providers to verify device and app authenticity.

Instead of blindly trusting incoming requests, App Check forces the client app to prove its identity before it even reaches your database or backend logic. Here are the default providers used for different platforms:

  • Android: Play Integrity API (replaces the deprecated SafetyNet). It checks if the app is installed from the Google Play Store and is running on a genuine, unrooted Android device.
  • Apple (iOS/macOS): DeviceCheck or App Attest. These Apple services cryptographically verify that the request is coming from a legitimate Apple device and your authentic app.
  • Web: reCAPTCHA v3 or reCAPTCHA Enterprise. It uses advanced risk analysis engines to distinguish between human (or legitimate browser) traffic and automated bots, without showing annoying image puzzles to the user.
  • Custom Providers: If you are building for platforms like desktop (Windows/macOS/Linux) or IoT devices, you can create a Custom Attestation Provider using your own backend logic.

The Workflow:

  1. Your app requests an attestation token from the platform's provider (e.g., Play Integrity).
  2. The provider evaluates the device and app integrity. If valid, it returns a short-lived App Check token.
  3. Your app attaches this token to every subsequent request made to Firebase services.
  4. The Firebase backend evaluates the token. If it's valid, the request proceeds. If not, it is instantly rejected.

Quotas and Limits (Token TTL)

When developers first hear about App Check, a common concern is: "Will my app hit the provider's API limits?" It is important to understand that your app does not contact the attestation provider for every single database read/write. Instead, the generated App Check token has a Time-To-Live (TTL). By default, this is 1 hour. The client SDK automatically caches this token and only requests a new one from the provider when it is about to expire.

However, your use of App Check is still subject to the quotas of the underlying attestation providers:

  • Play Integrity (Android): Has a default daily quota of 10,000 calls for its Standard API usage tier. If your app has a large user base and you expect to exceed this, you must request a daily limit increase. You can do this by directly submitting the Play Integrity API Quota Request form.
  • DeviceCheck / App Attest (Apple): Apple does not publicly publish hard limits for these services, but they are generally high enough to support massive, production-scale applications.
  • reCAPTCHA Enterprise (Web): Offers 10,000 assessments per month at no cost. Beyond that, standard Google Cloud pricing applies.

Behind the scenes, the Firebase SDK automatically refreshes the token before the TTL expires, requiring no manual intervention from the developer. If a network error occurs during this auto-refresh process, the pending request may fail, but it will be handled gracefully by standard Firebase SDK error mechanisms. This means you don't need to write custom token retry logic; you just handle standard Firebase network errors as you normally would.

Which Services Are Protected?

App Check has built-in, seamless support for several core Firebase products. Once enabled and enforced in the Firebase Console, it automatically intercepts and verifies traffic for:

  • Firestore Database & Realtime Database: Prevents unauthorized data access, scraping, and malicious database modifications.
  • Cloud Storage for Firebase: Protects your files and prevents attackers from exhausting your storage and download bandwidth quotas.
  • Cloud Functions for Firebase (callable functions only): Helps ensure that your callable endpoints are invoked only by legitimate clients. 
  • Firebase Authentication: Prevents malicious or automated account creation and protects your project from SMS fraud/abuse (especially critical and costly if you use Phone Authentication).
  • Firebase AI Logic: Protects your access to AI models, preventing unexpected billing spikes caused by bot-driven API calls.

What about Custom Backends?
You are not limited to Firebase services! If you have your own custom API (e.g., a Node.js, Python, or Go server), you can use the Firebase Admin SDK to intercept incoming requests and verify the App Check tokens before processing them.

How to Implement App Check

Implementing App Check in a Flutter application involves configuring your Firebase project and adding a few lines of code to your app's initialization.

Step 1: Register Your Apps

Before writing any code, you need to register your apps in the Firebase Console:

  1. Go to your Firebase project.
  2. Navigate to Build > App Check.
  3. Click on the Apps tab and register your Android, iOS, and Web applications with their respective providers (Play Integrity for Android, App Attest for iOS, and reCAPTCHA for Web).

Step 2: Add the Flutter Dependency

Add the firebase_app_check plugin to your Flutter project using your terminal:

flutter pub add firebase_app_check
Enter fullscreen mode Exit fullscreen mode

Step 3: Initialize App Check

In your main.dart file, you need to initialize App Check right after initializing Firebase. Specify which providers to use for each platform.

Future<void> main() async {
  // I assume you already added this line
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize Firebase First
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );

  // Initialize App Check
  await FirebaseAppCheck.instance.activate(
    // For Android: Default provider for Android is the Play Integrity provider.
    // You can use the "AndroidProvider" enum to choose
    // your preferred provider. Choose from:
    // 1. Debug provider
    // 2. Safety Net provider
    // 3. Play Integrity provider
    androidProvider: kDebugMode
        ? AndroidProvider.debug
        : AndroidProvider.playIntegrity,

    // For iOS: Default provider for iOS/macOS is the Device Check provider.
    // You can use the "AppleProvider" enum to choose
    // your preferred provider. Choose from:
    // 1. Debug provider
    // 2. Device Check provider
    // 3. App Attest provider
    // 4. App Attest provider with fallback to Device Check provider (App Attest provider is only available on iOS 14.0+, macOS 14.0+)
    appleProvider: kDebugMode
        ? AppleProvider.debug
        : AppleProvider.appAttest,

    // For Web: You can also use a `ReCaptchaEnterpriseProvider` provider 
    // instance as an argument for `webProvider`
    webProvider: ReCaptchaV3Provider('YOUR_RECAPTCHA_V3_SITE_KEY'),
  );

  runApp(const MyApp());
}
Enter fullscreen mode Exit fullscreen mode

Step 4: Applying App Check to Specific Services

For most core Firebase services (like Firestore Database, Realtime Database, and Cloud Storage), calling activate() in your main function is all you need. The SDKs will automatically handle the App Check tokens under the hood.

However, for certain specific services (like Firebase AI Logic), you might need to explicitly pass the App Check instance.

Here is a simple example showing how to use both Firestore Database and Firebase AI Logic in your app:

// 1. Firestore Database
// App Check is automatically applied under the hood.
// You don't need to pass the App Check instance manually.
final firestore = FirebaseFirestore.instance;

// 2. Firebase AI Logic
// You can explicitly pass the App Check instance to secure your AI calls.
final googleAI = FirebaseAI.googleAI(
  appCheck: FirebaseAppCheck.instance,
);
final vertexAI = FirebaseAI.vertexAI(
  appCheck: FirebaseAppCheck.instance,
);
Enter fullscreen mode Exit fullscreen mode

Step 5: Monitor and Enforce

Once you have released your app with the App Check SDK included, you should not enforce it immediately. Doing so would instantly block older versions of your app that do not have the App Check code yet.

Instead, go to the App Check dashboard in the Firebase Console and monitor your metrics.

Screenshot of the Firebase Console (Cloud Firestore) “App Check request metrics” panel for the last 7 days (Feb 21–Mar 1), showing 0 verified requests out of 200 total and 100% “Unverified: outdated client requests” (200/200), with the graph spiking to 100% near Mar 1 and an “Enforce” button at bottom right.

As you can see in the dashboard above, you can track the percentage of Verified requests, Unverified requests, and Invalid requests.

Once the vast majority of your traffic falls under "Verified requests" (meaning most of your users have updated to the app version that includes App Check), you can safely click the Enforce button for your backend services (like Firestore Database or Cloud Storage).

From that moment on, any unverified requests will be blocked by Firebase. If a client app attempts to access an enforced service without a valid App Check token, the request will be rejected, and the developer/user will encounter a permission error like this:

Uncaught Error in snapshot listener: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Enter fullscreen mode Exit fullscreen mode

Common Pitfalls & Troubleshooting

While App Check is relatively easy to set up, developers often run into a few common issues during development and deployment. Here are the most frequent ones and how to solve them:

  • "Missing or insufficient permissions" error after enforcement: If your app was working fine, but suddenly starts throwing FirebaseError: [code=permission-denied]: Missing or insufficient permissions. right after you clicked "Enforce" in the console.
    • Solution: This means App Check is actively blocking your requests. Ensure the App Check SDK is initialized properly before making any calls to Firebase services. Keep in mind that this exact error is also thrown by Firebase Security Rules, so if your security rules are correct, the culprit is likely a missing or invalid App Check token.
  • Testing on Emulators or Localhost fails: By design, App Check blocks traffic from unverified environments, such as the Android Emulator, iOS Simulator, or your local web server.

    • Solution: Use the Debug Provider for local development. When you run your app in debug mode (kDebugMode), the SDK prints a secret debug token to your IDE's console (or to Chrome Developer Tools for Web). It looks exactly like this:

      App Check debug token: 55183c20-de61-4438-85e6-8065789265be. You will need to add it to your app's App Check settings in the Firebase console for it to work.
      
    • You must copy this token, go to the Firebase Console > App Check > Apps, click on the three dots next to your app, and select Manage debug tokens.

      Screenshot of the Firebase Console's App Check page on the Apps tab, showing a registered

    • Paste your token there and save it. This will allow your local environment to bypass the attestation checks securely.

      Screenshot of Firebase Console “Manage debug tokens” dialog for App Check, showing fields to name a token (“My Token”) and a generated debug token value (a UUID), with a warning that the token won’t be shown again and buttons for Cancel and Save.

  • "Unverified: invalid requests" spikes on Android: This usually happens if there is a mismatch with your app signing keys.

    • Solution: Ensure that you have added all your SHA-256 fingerprints (both for debug and release keystores) to your Android app settings in the Firebase Console.
  • Breaking older app versions: Clicking the "Enforce" button too early locks out users who haven't updated their app in the App Store or on Google Play.

    • Solution: Always monitor your App Check metrics for a few weeks. Only enforce when the percentage of "Unverified: outdated client requests" drops to an acceptable minimum.

Conclusion

Firebase App Check is an essential, easy-to-implement security layer that protects your backend infrastructure from abuse, billing fraud, and unauthorized access. By ensuring that only your authentic apps can communicate with Firebase services, you add a critical safeguard alongside your existing security rules and authentication methods. Remember to monitor your traffic before enforcing it to ensure a smooth transition for your users.


Thanks for your time!

A special thanks to Rosario, who inspired me to write this article.

If you found this article helpful, stay tuned for my next post, where I'll dive into Firebase AI Logic and show how the App Check plays a critical role in securing your AI endpoints.

You can find me on Twitter and LinkedIn. You can find everything about me here.


References

Top comments (0)