Secure 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.

FirebaseAppCheckProvider.tsx
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:

_app.tsx
<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:

appcheck-middleware.ts
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!



Read more about Tutorials

Cover Image for Building an AI Writer SaaS with Next.js and Supabase

Building an AI Writer SaaS with Next.js and Supabase

ยท57 min read
Learn how to build an AI Writer SaaS with Next.js and Supabase - from writing SEO optimized blog posts to managing subscriptions and billing.
Cover Image for Announcing the Data Loader SDK for Supabase

Announcing the Data Loader SDK for Supabase

ยท8 min read
We're excited to announce the Data Loader SDK for Supabase. It's a declarative, type-safe set of utilities to load data into your Supabase database that you can use in your Next.js or Remix apps.
Cover Image for Adding AI capabilities to your Next.js SaaS with Supabase and HuggingFace

Adding AI capabilities to your Next.js SaaS with Supabase and HuggingFace

ยท20 min read
In this tutorial, we will learn how to use add AI capabilities to your SaaS using Supabase Vector, HuggingFace models and Next.js Server Components.
Cover Image for Building an AI-powered Blog with Next.js and WordPress

Building an AI-powered Blog with Next.js and WordPress

ยท17 min read
Learn how to build a blog with Next.js 13 and WordPress and how to leverage AI to generate content.
Cover Image for Using Supabase Vault to store secrets

Using Supabase Vault to store secrets

ยท6 min read
Supabase Vault is a Postgres extension that allows you to store secrets in your database. This is a great way to store API keys, tokens, and other sensitive information. In this tutorial, we'll use Supabase Vault to store our API keys
Cover Image for Introduction to Next.js Server Actions

Introduction to Next.js Server Actions

ยท9 min read
Next.js Server Actions are a new feature introduced in Next.js 13 that allows you to run server code without having to create an API endpoint. In this article, we'll learn how to use them.