When a CORS Error Isn't a CORS Error: Firebase Functions Walk Through

May 05, 2019

The transition to serverless platforms such as Google Firebase may eliminate much of the work required to manage our deployed code, but issues related to how our software is served can still arise. In fact, certain types of issues may even occur more frequently as we mix and match different cloud services to serve up our app. One issue that falls in this category is the infamous Cross-Origin Resource Sharing or CORS error. For many front-end developers CORS errors can be a major source of pain. If you have seen hours go down drain trying to debug a vague CORS message know that at least you’re in good company1. In this post I hope to help you understand what exactly a CORS error is, using a particular instance of it I’ve run into using Cloud Function’s on Google’s Firebase platform.

Why Cloud Functions?

While generally when using Firebase you can execute much or even all of the logic for your application directly from your client application, there are some actions that might require a privileged environment. For example, the firebase-admin library provides additional functionality not available in the Firebase Javascript SDK, such as the ability to manage other user accounts or generate custom authentication tokens.

Since the Admin SDK uses a service account and we wouldn’t want to make service credentials available directly in our client, Firebase Functions is an obvious choice to reach for. While the Firebase team continues to make strides in improving the tooling around Functions, the fact remains that running code in a serverless function complicates the debugging story. This post attempts to shine some light on one area where I’ve banged my head into this wall on more than one occasion.

Setup

Assuming you already have Firebase project setup using the Firebase Tools CLI but haven’t initialized functions yet, getting started is pretty simple. Running firebase init functions and following the prompts gives us a new functions subdirectory in our project with an index.js file. Uncommenting the example code provided should give us something like this:

functions/index.js
const functions = require('firebase-functions');

exports.helloWorld = functions.https.onRequest((request, response) => {
 response.send("Hello from Firebase!");
});

We can run our functions locally using the emulator provided by the Firebase CLI with the command firebase emulators:start. Using the browser or HTTP client of our choice we can verify our locally emulated function is returning the expected text response "Hello from Firebase!".

CORS

While for testing our function it can be useful to make a request directly to the corresponding endpoint, typically our request is going to actually be coming from our application. In this case that means a web app in the browser. We might add some JavaScript to our app to make a request to the function like so:

fetch('localhost:5001/<...>')
  .then(response => response.text())
  .then(text => console.log(text));

We can test this again using the emulators to serve our web app and functions. Once again we see the expected text rendered, this time in the browser console. So far so good, but if we try to deploy this code as is we’re going to run into a problem.

Image of a CORS error in the browser console with the error text: Access to fetch at 'https://us-central1-idempotency-playground.cloudfunctions.net/helloWorld' from origin 'http://localhost:5000' has been blocked by CORS policy: 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.

The browser is happy to make a request when we serve both the endpoint and our web app locally, but if we make a request against the deployed endpoint we get the error above. This can be one of the more frustrating aspects of CORS errors, as they may not appear in local testing and only to reveal themsselves once our code is deployed.

Updating our Cloud Function with code as below will resolve this issue and let us make requests to the deployed endpoint:

const functions = require('firebase-functions');
const cors = require('cors');
exports.helloWorld = functions.https.onRequest((request, response) => {
  return cors()(request, response, () => {    response.send("Hello from Firebase!");
  });});

CORS???

Alright, so we have Cloud Functions HTTP endpoint ready to be deployed, but it’s not really doing anything interesting yet. Let’s update our Cloud Function to perform some contrived operation using data passed in the request body:

exports.helloWorld = functions.https.onRequest((request, response) => {
  return cors()(request, response, () => {
    const name = request.body.friend.name;
    response.send(`Hello ${name} from Firebase!`);
  });
});

Still nothing very interesting going on here but it will allow us to see some strange errors. You’ll notice we are accessing a deeply nested property on the request object without performing any validation beforehand. This could result in an error at run time if the property friend is null or undefined. And indeed, if we deploy this new function and then attempt to call it from our web app without updating our request we do get an error. However, it might not be the error you’d expect.

Image of a CORS error in the browser's JavaScript console, much like the original CORS error we saw above

From glancing at the error above it’d be easy to assume that we somehow misconfigured our CORS policy. I’ve in fact made this mistake before, swapping cors() to cors({ origin: '*'}) to cors({ origin: true}) all to no avail. Pasting the error message into your favorite search engine will lead you even further astray2. Only when view the logs for our Cloud Function do we get a useful error message:

Image of Cloud Functions log from the Firebase console with the actual TypeError

From here it’s obvious what the problem is: an unhandled exception in our function short circuits the response from the Cloud Function, giving us the misleading CORS error we see in our front end.


  1. See, for example, Dan Ambramov’s Things I Don’t Know As Of 2018.

  2. This is what my colleague Kyra refers to as a ‘vortex’ in her excellent post here.

  3. For more on why it’d be bad to push this function to production as is see MDN’s CORS page here.


Written by nic wilson who lives and works in Minneapolis building useful things. You should follow him on Twitter