TutorialsSecure your Next.js application with Firebase AppCheck

Firebase AppCheck helps us protect our websites against bad actors such as automated bots. In this post, we integrate Firebase AppCheck in a Next.js application.

7 min read
Cover Image for Secure your Next.js application with Firebase AppCheck

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:

  1. a public key (on the client-side)
  2. 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:

  1. retrieving the site key constant appCheckSiteKey from the application's configuration
  2. initializing the AppCheck service using the Firebase method initializeAppCheck
  3. 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:

  1. First, we need to generate a token with AppCheck that we will send as a header to our API requests.
  2. 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!


Stay informed with our latest resources for building a SaaS

Subscribe to our newsletter to receive updatesor

Read more about
Tutorials

Cover Image for Creating a Waitlist with Firebase Auth

Creating a Waitlist with Firebase Auth

3 min read
Implement a waitlist sign-up with Firebase Auth and allow sign-ins in batches to your SaaS
Cover Image for Using ElasticSearch with Next.js

Using ElasticSearch with Next.js

8 min read
In this article, we share how to use ElasticSearch with Next.js to index your Firestore documents and make them searchable.
Cover Image for Using Firestore in Firebase Storage Rules

Using Firestore in Firebase Storage Rules

3 min read
Firebase Storage now allows you to use Firestore queries to in your security rules. Here is all you need to know!
Cover Image for Turn your Next.js application into a PWA

Turn your Next.js application into a PWA

4 min read
PWA can make your app look native, faster, updatable and offline-ready. In this post, we learn how to make a PWA with a Next.js application.
Cover Image for Email Link Authentication with Firebase and Next.js

Email Link Authentication with Firebase and Next.js

4 min read
Learn how to add Email Link authentication to your SaaS application with Firebase Auth and Next.js
Cover Image for Walkthrough: Starting a Makerkit project with Firebase and Next.js

Walkthrough: Starting a Makerkit project with Firebase and Next.js

9 min read
This walkthrough is a summary of the documentation to quickly bootstrapping a SaaS project with Makerkit