0

To protect myself and my project from fraud and abuse (especially since Firebase billing does not allow a hard limit) I would really like to protect the Cloud Function endpoints with AppCheck. The issue is, however, that there is a Chrome extension involved in this project, which calls a Callable Cloud Function.

I tried using the Firebase AppCheck library appCheck = initializeAppCheck(app, {provider: new ReCaptchaV3Provider(...), ...}) but it it loads external scripts and that is not allowed with the Content Security Policy of manifest V3. As also suggested here and here, it's not really possible to load reCAPTCHA in an manifest V3 extension (I only need it in the popup, not the content).

The extension requires users to authenticate with a Google account to be used but this (see comments, I'm wondering the same) and that post suggest that user authentication can only be checked within the Callable function, meaning the function will get invoked. So no option either.

So is there any option at all when using Firebase with a Chrome extension to protect myself from a malicious actor attacking the endpoints and creating an enormous bill for me?

1 Answers1

0

Okay after quite some hours I might have figured out a way. But would be helpful if someone more knowledgeable could confirm.

The solution is to create a custom AppCheck token (docs) which is then passed along with the custom claims of the Firebase user auth object.

Only this allows to close down all Cloud Function endpoints as in other scenarios a client token is always fetched from a server, which then leads back to having an open endpoint somewhere.

The token then needs to be refreshed by a scheduled periodic function and attached to the custom claims. So when a user signs in on the browser extension, they always obtain a valid AppCheck token along with their id_token.

On the backend it would look like this:

const appCheckTokenResult = await appCheck.createToken(appId, { ttlMillis: someExpiration);
const appCheckToken = appCheckTokenResult.token;
const appCheckTokenExpiration = appCheckTokenResult.ttlMillis / 1000;

await auth.setCustomUserClaims(uid, {
  appCheckToken: appCheckToken,
  appCheckTokenExpiration: appCheckTokenExpiration,
});

The AppCheck provider is not shared between popup and background, so it needs to be initialized in boths contexts. On the extension client it would look like this:

const appCheckCustomProvider = new CustomProvider({
  getToken: () => {
    return new Promise((resolve, _reject) => {
      const appCheckToken = {
        token: appCheckToken, //from custom claims
        expireTimeMillis: appCheckTokenExpiration, //from custom claims
      }

      resolve(appCheckToken)
    })
  },
})

appCheck = initializeAppCheck(app, {
  provider: appCheckCustomProvider,
})