Firebase AppCheck when working with Emulator on localhost
Asked Answered
K

3

8

How are developers working with Firebase App Check when developing locally using the emulator on localhost? Are you disabling App Check on localhost entirely? Or are you able to emulating App Check locally?

Firebase has some instructions on using App Check with a debug provider, but the use case for that seems to be when you want to debug locally but use GCP's backend services in the cloud. It doesn't look relevant for developing against the emulator.

Running this in the client fails recaptcha app attestation with a 403 response (PERMISSION_DENIED), presumably because localhost is not listed as an allowed domain:

  const appCheck = firebase.appCheck();
  appCheck.activate(
    process.env.REACT_APP_FIREBASE_APP_CHECK_SITE_KEY,
    true,
  );

When enforcing app check in callable functions, context.app is undefined when running in the emulator so requests will fail app check.

Disabling App Check locally is certainly an option, but was wondering if there was a way to emulate app check as well.

Key answered 23/11, 2021 at 16:31 Comment(0)
M
3

I've got it set up, but not without lots of trial and error.

Try adding this snippet right above your call to active appCheck. It seems it needs come before activating appCheck. I was getting that same error until I move the debug snippet before the active call. I am using web version 9 though... not sure if that makes a difference.

if (process.env.NODE_ENV !== 'production') {
  self.FIREBASE_APPCHECK_DEBUG_TOKEN = true;
}

const appCheck = firebase.appCheck();
appCheck.activate(
  process.env.REACT_APP_FIREBASE_APP_CHECK_SITE_KEY,
  true,
);

This will print a token to the console which will need to be added to you Firebase project settings. Like described in the link you provided.

Did you do those 2 steps and still get the 403 response?

Melindamelinde answered 24/11, 2021 at 2:51 Comment(11)
Just to confirm, are you using this when you're testing with the Firebase Emulator backend? In other words, the Emulator is verifying that app check token for things like callable functions? I thought self.FIREBASE_APPCHECK_DEBUG_TOKEN was for when you wanted to test your client locally while using the backend in GCP's cloud. – Key
Hmmm... that may be true about the token. I'm using an Express App on Cloud Run for my backend and have app check working on my server and clients using emulators for all while developing locally. I've not set app check up for a callable function on the emulators. I saw this which I'll share in case you didn't come across it yet firebase.google.com/docs/app-check/cloud-functions It looks like once you have appcheck configured on your client (not getting the 403) the token should show up on the context object of the callable function on the backend. It should work with the emulators too – Melindamelinde
I do get your point though about it not seeming like its needed for your use case. If everything is running locally why would the debug token be needed? I'm still learning all the in and outs of appCheck and it seems like a great feature. I'm wondering if it could replace my need for using CSRF. It seems like appCheck is doing the same thing and more. – Melindamelinde
@BrianKernan With App Check in place, I don't think CSRF is possible anymore by a malicious site, as there shouldn't be a way for that malicious site to construct the app check token to send with malicious requests. As for the debug token, I haven't had a chance to check whether the emulator will validate the token generated. It would be strange to register the token in project settings in the cloud, as the emulator shouldn't talk to the cloud to validate such things. As for your link in your comment, it's the same one that I included in my original question πŸ˜‰. – Key
@JohnnyOshika did you find a solution for the problem of emulator functions failing when context.app is undefined? this is driving me up the wall. – Kronick
never mind, just found out about the FUNCTIONS_EMULATOR environment variable which is true when a function is running on the emulator. so if (!context.app) { <fail> } becomes if(!context.app && !process.env.FUNCTIONS_EMULATOR) { <fail> }. – Kronick
@JohnnyOshika thanks Johnny... that was my thought about not needing to use CSRF and app check. Also, I realized I gave you the pretty same information from your original post after I posted my reply... sorry about that. – Melindamelinde
@Kronick I'm pretty much doing the same thing, although I wrapped the App Check in a middleware so that I'm not repeating it on every callable function. I documented how to do that here: https://mcmap.net/q/1471497/-middleware-for-firebase-callable-functions – Key
@JohnnyOshika did you find a way to test with Firebase Emulator? I am facing the exact issue and not sure how to do local development with App Check. Thanks for any direction to make it work. – Leonilaleonine
@CupidChan I'm ignoring AppCheck when running locally against a middleware. I haven't found a better solution. Have you? – Key
@JohnnyOshika I only disable the AppCheck when I use an emulator. – Leonilaleonine
A
1

Firebase emulator doesn't validate the token, as long as there is one present. So I leave my callable functions ready as if they are in production.

export const myCallableFn = onCall(
{
    cors: [],
    enforceAppCheck: true,
  },
  async (request) => {
    // some business logic
  }

And on my frontend logic I create a fake Attestation provider when the client code in running on development mode:

window.onload = () => {
      console.log("window loaded");
    
      if (process.env.NODE_ENV === "development") {
        console.log("testing locally -- hitting local auth and firestore emulators");
        connectAuthEmulator(getAuth(), "http://localhost:9099", {
          disableWarnings: true,
        });
        connectFunctionsEmulator(functions, "localhost", 5001);
        initializeAppCheck(app, {
          provider: new CustomProvider({
            getToken: () => {
              return Promise.resolve({
                token: "fake-token",
                expireTimeMillis: Date.now() + 1000 * 60 * 60*24, // 1 day
              });
            }
          }),
    
          isTokenAutoRefreshEnabled: true,
        });
    
      }
      else {
        initializeAppCheck(app, {
          provider: new ReCaptchaEnterpriseProvider(
            "my-production-key"
          ),
          isTokenAutoRefreshEnabled: true,
        });
      }
    };

This way I can use the emulator with AppCheck setup even without internet connectivity

Antoinetteanton answered 24/1 at 4:1 Comment(0)
N
0

The simplest method I've found is to use FUNCTIONS_EMULATOR environment variable.

export const myCallableFn = onCall(
  {
     // Enforce App Check unless running in the emulator for local development
    enforceAppCheck: process.env.FUNCTIONS_EMULATOR !== 'true' 
  },
  async (request) => {
    try {
      // Perform business logic
      return { data: "data" };
    } catch (error) {
      return { error: error.message };
    }
  }
);
Natka answered 8/9 at 20:42 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.