Firebase AppCheck is a service provided by Firebase to protect websites against bad actors, such as spammy users or automated bots.
Under the hood, Firebase AppCheck uses Google Recaptcha v3, an automated version of the popular quizzes that can detect bad actors without the need of completing a quiz. In fact, Recaptcha v3 works in the background, and never interrupts the user.
The service is free and helps us protect Firebase Authentication, Firebase Storage, Firebase Firestore and any custom API against threats and disallowed usage of your service.
Obviously, this all sounds great and will surely help you sleep better at night. With that said, you should remember to take into account the implications of using this library, both in terms of bundle size and for your users' privacy. This is why Makerkit disables Firebase AppCheck by default but allows you to easily opt in.
Registering your website for Recaptcha v3
To register your website for Recaptcha v3, you need to complete the linked form. After finishing entering the details, you'll be provided with a Site Key
which we use to initialize the Firebase AppCheck service.
NB: when registering the website, make sure to create a Recaptcha v3 key, not v2.
After submitting the form, you will receive two keys:
- a public key (on the client-side)
- a private key (to be used on the server side)
Save the public key and add it to your Next.js .env
file:
NEXT_PUBLIC_APPCHECK_KEY=<YOUR_KEY>
Enabling Firebase App Check from the console
First, we need to enable Firebase App Check from the Firebase Console to register our application using the keys we have generated in the previous step.
Above, you should have added the secret key that we have generated. If it all went well, you have successfully enabled app-check for your application 🎉!
Enforcing Firestore to use App Check
To enforce Firestore to always validate requests using AppCheck, you will have to enable it from the Console. From where you are, click on the API
tab, then click on Firebase Firestore
. You will then be presented with the popup as in the image below:
Continue and enforce Firestore to use AppCheck.
Adding Debug Tokens during development
Debug Tokens allow us to use App Check while working in a local development environment or while running automated tests in a CI.
Debug Tokens work well when you're running a Firebase project with real credentials. Instead, if you're running a development project with a demo-
prefix, it will not work. These projects require little to no configuration, so they're ideal to get started, but not for testing Firebase's real services.
To create a Firebase Debug Token, click on the registered application menu, and you should then see the window as in the image below:
Once created, copy the debug token and add it as an environment variable to your application:
NEXT_PUBLIC_APPCHECK_DEBUG_TOKEN=<YOUR_DEBUG_TOKEN>
Adding Firebase AppCheck to your Next.js App
It's now time to add the Firebase AppCheck library to our Next.js application, so we can protect it from abuse. To initialize Firebase AppCheck we will use reactfire
, the official Firebase library for React applications.
Assuming you already created a Next.js application, ensure to install Reactfire
and initialize your Firebase application.
Creating the FirebaseAppCheckProvider component
Then, we create a component named FirebaseAppCheckProvider
, responsible for:
- retrieving the site key constant
appCheckSiteKey
from the application's configuration - initializing the AppCheck service using the Firebase method
initializeAppCheck
- Providing the provider
AppCheckProvider
Something very important to remember is to initialize this component before accessing any service, such as Firebase Auth or Firebase Firestore, otherwise, the requests to these services will fail.
import { AppCheckProvider, useFirebaseApp } from 'reactfire';import { initializeAppCheck, ReCaptchaV3Provider } from 'firebase/app-check';import configuration from '~/configuration';const FirebaseAppCheckProvider: React.FCC = ({ children }) => { const siteKey = process.env.NEXT_PUBLIC_APPCHECK_KEY; const app = useFirebaseApp(); if (!siteKey || !isBrowser() || configuration.emulator) { return <>{children}</>; } if (!configuration.production) { attachAppCheckDebugToken(); } const provider = new ReCaptchaV3Provider(siteKey); const sdk = initializeAppCheck(app, { provider, isTokenAutoRefreshEnabled: true, }); return <AppCheckProvider sdk={sdk}>{children}</AppCheckProvider>;};export default FirebaseAppCheckProvider;
To attach a debug token, we need to define the following function:
function attachAppCheckDebugToken() { const token = process.env.NEXT_PUBLIC_APPCHECK_DEBUG_TOKEN; Object.assign(window, { FIREBASE_APPCHECK_DEBUG_TOKEN: token, });}
Using the FirebaseAppCheckProvider component
To use this provider, import it and place it below the FirebaseApp
provider. For example, we assume you're initializing Firebase in _app.tsx
.
Below is an example of how the Makerkit SaaS starter renders the main application component:
<FirebaseAppShell config={firebase}> <FirebaseAppCheckProvider> <FirebaseAuthProvider> <FirebaseAnalyticsProvider> <Component {...pageProps} /> </FirebaseAnalyticsProvider> </FirebaseAuthProvider> </FirebaseAppCheckProvider></FirebaseAppShell>
If everything went well, AppCheck will now be working on your local Firebase web application.
NB: while running the emulators using a demonstration project (such as demo-makerkit
) AppCheck will not be working.
Protecting Next.js API endpoints with Firebase AppCheck
Did you know that we can use Firebase AppCheck to protect a custom backend like the Next.js API endpoints? Well, yes, and it's also fairly simple.
Here is how we do it:
- First, we need to generate a token with AppCheck that we will send as a header to our API requests.
- The API endpoint is responsible for checking the validity of the token before handling the API handler. If invalid, the request is rejected.
Sending App Check token along your API requests
The implementation to send a token along your API request may differ depending on how you request your API endpoints, but we will show a fairly simple example so that you can adapt it to your own implementation.
Below is a simple React hook to request a Firebase AppCheck token that we will send along as a header.
It's important to notice that we will not use the hook useAppCheck
from reactfire
, because of a few drawbacks:
- it throws an error when not initialized
- we cannot call it conditionally
Therefore, we initialize the SDK manually through its context AppCheckSdkContext
, so that we can control its behavior: in our case, we want to fetch the token only when the SDK is provided.
function useGetAppCheckToken() { // instead of using useAppCheck() // we manually request the SDK // because we *may not have initialized it* const sdk = useContext(AppCheckSdkContext); return useCallback(async () => { try { // if the SDK does not exist, we cannot generate a token if (!sdk) { return; } const forceRefresh = false; const { token } = await getToken(sdk, forceRefresh); return token; } catch (e) { return; } }, [sdk]);}
Now, let's make a simple fetch implementation that takes the token and sends it along. the below is extremely simplified, so please don't copy it literally.
All you need to know is that we need to send the result of the getAppCheckToken
promise to be included in the headers
of our API request.
async function useFetch<Resp = unknown>( url: string, payload: string, method = 'POST', headers?: StringObject) { const options: RequestInit = { method, headers: { accept: 'application/json', 'Content-Type': 'application/json', ...(headers ?? {}), }, }; const getAppCheckToken = useGetAppCheckToken(); const token = await getAppCheckToken(); // if the app-check token was found // we add the header to the API request if (token) { headers['X-Firebase-AppCheck'] = token; } try { const response = await fetch(url, options); if (response.ok) { return (await response.json()) as Promise<Resp>; } return Promise.reject(response.statusText); } catch (e) { return Promise.reject(e); }}
Adding a middleware to protect API routes with AppCheck
To do so, we can create a simple API middleware that checks if the request is successful. If not, it will reject the request before the API handler is executed.
Below is our middleware:
const FIREBASE_APPCHECK_HEADER = 'X-Firebase-AppCheck';export async function withAppCheck( req: NextApiRequest, res: NextApiResponse) { const appCheck = getAppCheck(); const token = req.headers[FIREBASE_APPCHECK_HEADER]; const forbidden = () => forbiddenException(res); if (!token || typeof token !== 'string') { return forbidden(); } try { await appCheck.verifyToken(token); } catch (e) { return forbidden(); }}
How to use it?
1) Simply use it within your handlers
export default function(req, res) { await withAppCheck(req, res);}
2) Use it as a chain of middlewares
export default withMiddleware( withAdmin, withAppCheck, withAuthedUser, (req, res) => { // write here your API handler })
Conclusion
Many developers are very worried about using Firebase due to security reasons. AppCheck is one of the ways we can protect our applications against abuse, spammy users and automated bots.
Hopefully, this guide will help you improve the security of your Firebase applications and make you sleep better at night.
If you're looking for a complete example of the code above, consider purchasing a copy of our Next.js and Firebase SaaS starter, which will help you get started in no time!