简体   繁体   中英

Preflight error on Firebase cloud functions

I'm facing a preflight error when I try to call a cloud function of mine from my website. I implemented the cors module in my cloud function, and my request got the cors header authorizations

The cloud function:

const cors = require('cors')({ origin: true });
exports.CLOUDFUNCTION = functions.https.onRequest(
  (request: any, response: any) => {
    cors(request, response, async () => {
      response.status(200).send('hello');
    })
  }
);

The website request:

fetch('FIREBASE_URL/CLOUDFUNCTION',
          {
            method: 'POST',
            headers: {
              'Content-Type': 'application/json',
              'Access-Control-Allow-Origin': '*',
              'Access-Control-Allow-Headers': 'Content-Type',
              'Access-Control-Allow-Headers': 'Authorization'
               
            },
            body: JSON.stringify(body), // body is a simple {"variable": "value"}
          }
        );

The error

Access to fetch at 'FIREBASE_URL/CLOUDFUNCTION' from origin 'MYWEBSITE' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

If you are getting a 403 Forbidden Error when trying to access your function via it's URL, you have a problem with your function's deployment, it's configuration or you have made a mistake in the URL.

Note: While I use "traditional" require statements here to match your example, I encourage you to use newer ES6+ JavaScript features ( const , let , async / await , import , etc.) for any newly written functions.

Deploy using the latest firebase-tools version

Make sure you are deploying using the latest version of the firebase-tools CLI.

When v7.7.0 of firebase-tools released (Jan 15, 2020), the way Cloud Functions are invoked on the server changed so that functions could be invoked by only authenticated users. To be accessible to Firebase Users, these functions must be made public by explicitly granting the allUsers group the Cloud Function Invoker permission.

In v7.7.0 and later, this is done for you as part of deployment. However, if you deploy functions using an older version, you will need to configure this permission yourself or redeploy using a newer firebase-tools version.

Check the exported function name

Make sure the function you export is named what you expect once deployed.

In particular, pay close attention to when your function is exported as part of a function group either deliberately or accidentally. This often turns up when you've split your functions into multiple files. In the below code blocks, CLOUDFUNCTION gets exported as myFunctions-CLOUDFUNCTION and not just CLOUDFUNCTION as you may expect.

// myFunctions.js
exports.CLOUDFUNCTION = functions.https.onRequest(...);
// index.js (incorrect)
exports.myFunctions = require("./myFunctions.js");
// index.js (correct)
const myFunctions = require("./myFunctions.js");

exports.CLOUDFUNCTION = myFunctions.CLOUDFUNCTION;

Check the function's URL

Check the Cloud Functions URL you are using for typos. Function names in Cloud Functions URLs are case-sensitive.

The correct URL should follow the format:

https://<REGION>-<PROJECT_ID>.cloudfunctions.net/<EXPORTED_FUNCTION_NAME>

Example:

https://us-central1-fir-sandbox.cloudfunctions.net/echo

Handle CORS errors & stop processing

In your code example, you pass in the NextFunction without an error handler. While using { origin: true } , this is "fine", but you'll start running into trouble when you start restricting the origins you call your function from. This is particularly handy for preventing your functions being invoked directly by their URL (where origin would be undefined ). Take a look at the documentation or the next section for more info.

const cors = require('cors')({ origin: true });
exports.CLOUDFUNCTION = functions.https.onRequest(
  (request, response) => { // <-- don't use `: any` here, as you are disabling the built-in types provided by firebase-functions
    cors(request, response, async (err) => {
      if (err) {
        // Denied by CORS/error with CORS configuration
        console.error("CORS blocked request -> ", err);
        response.status(403).send("Forbidden by CORS");
        return;
      }

      response.status(200).send('hello');
    })
  }
);

Optional: Tighten the cors configuration

While you can reflect the Access-Control-* headers using the cors package, consider explicitly setting these server-side.

const { projectId: PROJECT_ID } = JSON.parse(process.env.FIREBASE_CONFIG);

const cors = require('cors')({
  // during emulation, allow localhost & calling directly (i.e. no origin specified);
  // at all other times, restrict to deployed hosting sites only
  origin: process.env.FUNCTIONS_EMULATOR === "true"
    ? /^(https?:\/\/localhost:\d+|undefined)$/
    : [`https://${PROJECT_ID}.firebaseapp.com`, `https://${PROJECT_ID}.web.app`],
  allowedHeaders: ['Content-Type', 'Authorization']
});

exports.CLOUDFUNCTION = functions.https.onRequest(
  (request, response) => {
    cors(request, response, async (err) => {
      if (err) {
        // Denied by CORS/error with CORS configuration
        console.error("CORS blocked request -> ", err);
        response.status(403).send("Forbidden by CORS");
        return;
      }

      response.status(200).send('hello');
    })
  }
);

This simplifies your client-side code:

fetch('FIREBASE_URL/CLOUDFUNCTION',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify(body),
  }
);

Optional: Use Callable Functions

If your functions will require you to do actions on behalf of a user, you could make use of Callable Cloud Functions instead of the more bare-bones HTTPS Request functions. This version of a HTTPS Function handles CORS, authentication, and supports Promise-based returning of data.

Note: This will still require the function to be public as described above.

On the server side:

exports.CLOUDFUNCTION = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    // users must be logged in
    throw new functions.https.HttpsError(
      'failed-precondition',
      'The function must be called while authenticated.'
    );
  }

  if (data.variable === undefined)) {
    throw new functions.https.HttpsError(
      'invalid-argument',
      'Parameter "variable" must be a string'
    );
  }

  // you can return a promise here
  // this sends back the JSON string "hello world"
  return "hello world";
});

On the client side:

const callFunction = firebase.functions().httpsCallable('CLOUDFUNCTION');

callFunction(body)
  .then(
    (responseData) => {
      // TODO: handle response
    },
    (functionError) => {
      // TODO: handle error
    }
  );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM