Hardening your App
This is going to be a lengthy, but important post. My apologies if it is too long. Read it in steps.
It can be exciting to see your app in action; adding features and improving the user interface is immediately rewarding. But there is also some safety work that should happen behind the scenes. This is usually referred to as 'hardening' your app, making it less prone to cyberattacks.
I am not worried about hackers trying to cheat during a classroom game; that is unlikely to happen, and should not cause you concern. I am more worried about leaked API keys and malicious actors creating artificial usage (and therefore bills) for your apps.
An API key is essentially a long string of letters and numbers that allows your app to use an online service you signed up for. Anyone with your API key can access that service; if you, for example, have an API key for Claude or Codex and that key leaks, anyone can use Claude or Codex on your account. If you, e.g., have $50 worth of tokens in your account, any malicious actor with your API key can use those $50. So protect your API keys, don't preload too much money onto online services, and don't autoreplenish (unless absolutely necessary).
The issue of API keys has been circulating for a while. If you work with GitHub, you regularly push your code into your online repo. If your code contains the API keys, they have been leaked, particularly if your online repo is public. Here are some lines of defense:
- Use gitignore. You usually keep a local .env file that provides the app with any environmental variables it needs, like API keys. This .env file should never be pushed to your repo. Instead, create an .env.example file that looks like your .env file, except that it does not contain any real keys/numbers. Then, simply tell your AI agent to scan your codebase and add anything that should not be pushed to GitHub to your .gitignore file - this will avoid pushing your .env (and many other things) out to GitHub.
- Use Firebase Secrets. Firebase allows you to keep API keys as an encrypted secret on the server. If you, for example, call
firebase functions:secrets:set <your API key>, you will go through a dialog to set this key once for your app. You can now instruct the AI to make sure that all functions in your app know what secrets they need. The key never appears in your source code, is not written to your disk, and can never appear in any deployment artifacts.
This is just a first step. The next step, if you use Firebase in particular, is to limit the possibility of fraudulent abuse. Firebase, for all its strengths, has one big disadvantage: your billing has no upper limit if your app runs on the Blaze plan. That means that a malicious actor could ramp up costs for you by maliciously spamming your app. If you just use the Spark plan, you have no cost (and no billing), but you cannot use Firebase Functions (everything has to be done on the client side), and you risk your app simply stopping in the middle of a session.
An immediate question is whether there are alternatives that allow a billing maximum to limit risk; I have looked into them. The most immediate one is Supabase, which does allow setting a monthly maximum charge. However, the actual costs for Firebase in my apps are generally <$1 per month; Supabase requires a $25/month subscription for realistic use cases. So, without malicious actors, Firebase is the better option. You can also go with SQL-based alternatives like Heroku, which charges at least $15 per month. You can look into Cloudflare. It is free for static sites, and for dynamic sites, $5 per month will probably cover your App, but it also does not offer an easy way to cap monthly spending.
So Firebase remains a cheap but slightly risky option. Here are several more steps to harden your App in Firebase.
The first step is to set up and enable App Check. When your app's frontend calls Firestore, Firebase Auth verifies the user — but it says nothing about the client. Anyone who pops open your JavaScript bundle can grab your Firebase config (the API key, project ID, etc.), point their own script or command at your Firestore directly, and start hammering reads and writes against your security rules. App Check adds a second layer: a cryptographic attestation that the request is coming from your app running in a legitimate environment, not from a script someone wrote against your project.
The first thing App Check requires is a reCAPTCHA key. There are basically two types - Enterprise and Personal V3. You generally only need the Personal one, and you can easily get one at https://www.google.com/recaptcha/admin/create. Associate it with your Firebase project, and make sure that the URLs your web app uses are registered for that key. You will receive a Site Key and a Secret Key. The Site Key goes into your App .env file, as an entry like VITE_RECAPTCHA_SITE_KEY=<your key>. The Secret Key is registered in your Firebase console. Go to App Check -> Apps -> Your App -> reCAPTCHA to enter the key.
You should also create a local Debug Token. This essentially serves as an alternative for your reCAPTCHA key if you run your App on a local development server. To create such a token, go to App Check -> Apps -> Your App, and click on the three little vertical dots on the side (Open Menu). There, you can add and create a Debug Token. Copy it, and add it to your .env file with the line VITE_APPCHECK_DEBUG_TOKEN=<Your Debug Token>.
The next phase is to make sure that App Check works. For that, go to App Check -> APIs and look for two entries: Cloud Firestore and Authentication. If you click on either, you will see a time series of requests to the App and their Verification Status. Test out the App with your reCAPTCHA token. Then look at these time series - if you can see Verified requests from your test here, it means that your reCAPTCHA setup worked. You can keep testing for a bit, but once you're confident the setup works, click the Enforce button for both Cloud Firestore and Authentication.
The last step for App Check is to enable it for your functions. Go to your AI agent and tell it to ensure that App Check is enabled for all your functions. It will make the necessary changes in your code. You will need to redeploy your functions. Once you have done that, test your app thoroughly one more time to make sure everything works as it should. If things look good - voila, your app is a lot safer now.
The next standard step is to create a Kill Switch for your app. This is the only hard stop that will ultimately prevent excessive fraudulent charges. Unfortunately, Google does not make this step easy. This could be a simple setting. But you have to jump through a bunch of hoops to activate it. It essentially relies on you setting a budget, e.g., $25 a month. When Google detects that your app exceeds this budget, the Kill Switch is triggered, and your billing account is detached from the App. Note that this is not instantaneous - you may already see budget overruns before the detachment happens. But it is a hard stop on any fraudulent spending.
Here is what you need to do. First, you should install the Google Cloud SDK. Otherwise, you have to navigate the endless menus of the Google Cloud console (not very user-friendly from Google). With the Google Cloud SDK, there are simple terminal commands you can use instead. You can find the SDK here:

As a first step after installing the SDK, record your Billing Account (gcloud billing accounts list) and your project ID (gcloud config get-value project). Make sure you work in the right project (gcloud config set project <your-project-id>).
The second step is to enable Google Cloud APIs in your project by calling gcloud services enable cloudbilling.googleapis.com pubsub.googleapis.com cloudfunctions.googleapis.com. The key API here is cloudbilling.googleapis.com, which is what the Kill Switch will actually use to disable billing.
As a third step, you need to create a Pub/Sub topic through gcloud pubsub topics create billing-kill-switch. This essentially allows your budget to send an alert message to the Kill Switch cloud function.
Fourth, you need to create the actual Kill Switch function. This is the prompt I usually use to tell the AI to build the function:
I want to create a kill switch function for this app to avoid fraudulent billing. I have already installed APIs and created a Pub/Sub topic called billing-kill-switch. Can you create the function?
You also need to install the billing client in your functions directory. The AI will usually already do this for you, but in case you do not see it in the AI output, go to your functions directory (cd functions) and call npm install @google-cloud/billing. You can now deploy your functions (firebase deploy --only functions).
Still with me? Then let's get ready for Step 5. This step provides your default service account with the ability to disable billing. First, you need to find the email address of your runtime service account. You can do this using the console command gcloud functions describe <name of your kill switch function> --region=us-central1 --gen2 --format="value(serviceConfig.serviceAccountEmail)". If you do not know the name of your Kill Switch Function, watch the results from your most recent function deploy. The name of your function should be listed there. Then, to grant your service account the proper rights, call gcloud projects add-iam-policy-binding <PROJECT-ID> --member="serviceAccount:<SERVICE-ACCOUNT-EMAIL>" --role="roles/billing.projectManager".
Now, in the last step, you need to create (or modify) the budget for your project. Go to the Cloud Console (https://console.cloud.google.com/) and select your Project. Then click on Billing, and go to your Linked Billing Account. On the left-hand side, you should see an entry called 'Budgets & Alerts'. Click on it. If you have already created a budget for your project during Firebase setup (which is common), it should show up on the right-hand side. Click on it. (Otherwise, create a new budget). Be sure to double-check the total amount (i.e., when the kill switch will trigger) - if you set it too low, you will get false positives, and if you set it too high, you will get false negatives. Importantly, check the box that says 'Connect a pub/sub topic to this budget', and select the Billing Kill Switch topic you created earlier. The click save. If everything went well, this should have created a kill switch for your app, but test it to be certain. I have never really tested my kill switches, and I only hope that they work as intended.
These are the more structured ways to harden your app. You probably still have vulnerabilities, particularly in your Firestore rules. I usually provide the following prompt to my AI to find any further weaknesses:
I am worried that a malicious actor could try to drive up my costs for this app. At the same time, I want student access to be unchange through anonymous login. Provide suggestions on how to further harden the app against malicious actors driving up my costs.
This will usually unearth another range of things that you can address. Be sure to go step by step and test the app after each step to ensure that a hardening step does not break it.
I know this was a long post. Thanks for staying with me. This should help you build safer apps on Firebase. Nothing is perfect, and don't see my post here as any guarantee. But it should be helpful. Good night and good luck!
