Firebase Callable Function + Cors

Firebase Callable Function + CORS

For anybody else who has arrived here searching firebase callable functions cors errors, here's my checklist:

  1. Ensure the function is deployed.
  2. Ensure the function name is correct. I was calling recalculatY when it should have been recalculateY. Got a cors error for some reason.
  3. Ensure the function code itself is not throwing an error. Use the emulator to help. This didn't throw a cors error still helpful to know.
  4. Ensure your regions match - I am using europe-west2. I had to both deploy the function with that region, and call it using the region. For a while, I assumed the client would infer the correct region if the function name was correct. That was not the case.
  5. This last point is easy to oversee as everything else in the Firebase universe is more or less plug-and-play, but you need to make sure your functions are accessible by giving them the right permission. (This point is taken from @kitson's answer below. As many only read the first answer I think it is important to highlight this.)

Deploying a callable function to a specific region:

// This is the file in which you define your callable function.
const functions = require('firebase-functions');
...
exports.yourFunc = functions.region('europe-west2').https.onCall(async (data, context) => {
...
})

Calling a function in a specific region from the client (in this case, a vuejs web app):

// In my case, this is a vuex store file, but it is safe to assume this is plain old javascript
import firebase from 'firebase/app'
import 'firebase/functions'
...
firebase.app().functions('europe-west2').httpsCallable('yourFunc')

Note: firebase.app().function... vs firebase.app.function...

Firebase callable cloud functions security & CORS

You are using Firebase HTTPS Callable Functions which has a feature of Firebase Authentication to authenticate users to your app.

But you want to make your Cloud Function private in order to be secure and not accessed publicly.

=======EDIT=======

  • Why your URL is not accessible after making your Cloud Function private?

    Since the Cloud Function is private, it needs Authentication to be accessed. You will not be able to access the Cloud Function endpoint from the browser since it needs the Authorization token related to the IAM user or service account who has the permissions to invoke the Cloud Function. And the browser is not providing an authorization token automatically.

  • You are getting the CORS issue on the client side.

    I have reproduced the issue by creating a private Cloud Function ( by removing the AllUsers Member from the Cloud Function Invoker Role) and calling it from a Firebase app. And I indeed was getting the CORS issue.

In order to avoid CORS, I tried to add the Cloud function as a Firebase Hosting rewrite in firebase.jon :

Note: Firebase Hosting supports Cloud Functions in us-central1 only so this option will not be a solution for you. However I did the following test with Firebase Hosting in us-central1

"rewrites": [

{
"source":"/test",
"function":"test"
}
]

Also, I changed the Cloud functions code to verify ID tokens

Cloud function

const cors = require('cors')({origin: true});
const functions = require('firebase-functions');

var admin = require("firebase-admin");

var serviceAccount = require(".ServiceAccount.json");

admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "DATABASE_URL"
});

exports.test = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const tokenId = req.get('Authorization').split('Bearer ')[1];

return admin.auth().verifyIdToken(tokenId)
.then((decoded) => res.status(200).send(decoded))
.catch(function(err) {
console.error(err);
console.log(err);
res.status(401).send(err)});
});
});

Client Side:

firebase.auth().currentUser.getIdToken(true)
.then(function(idToken){
console.log('Id token is..');
console.log(idToken);

fetch('Cloud_Function_endpoint',{
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + idToken
},
method: 'post',
body: JSON.stringify({ data })
}).then(result => {
console.log(result);
}).catch(error => {
console.log(error);
});
}).catch(function(error){
console.log(error);
});

So after making these changes, I was no longer getting the CORS issue however I was getting the 401 (Unauthorized) error.

Solutions Tested:

  • We can’t use Firebase Hosting, as there is no Firebase Hosting service account to add roles/permissions since requests are originated from clients.

  • To secure a Cloud Function, an alternative is to enable the Domain Restriction Sharing Policy (DRS) and this way we will not be able to serve public Cloud Functions.

  • But the previous is the same as removing the AllUsers from a Cloud Function making it private.

  • I was able to find that for the private functions, we can specify an IAM policy for a given user / service account. And to authenticate the requests from the client, we will need to supply a bearer token associated with the user / service account.But this also can’t be achieved since the bearer token required for IAM is too short lived to be used in firebase.json file.

Conclusions:

  • It is not possible to access a private Cloud Function from a Firebase app.
  • The only solution is to have a public Cloud Function.

Firebase Callable Function: How to restrict CORS origin

Unfortunately, what you want is not possible. Response headers for callable functions only allow Content-Type and an optional charset. If you need control and restrict CORS or any part of the protocol that deviates from what's implemented by Firebase Callable Functions, you should instead use a normal HTTP function and implement those details as mentioned in this answer.

To handle a preflight request using HTTP function, you must set the appropriate Access-Control-Allow-* response headers.

Here's a sample nodejs code that handles preflight request:

/**
* HTTP function that supports CORS requests.
*
* @param {Object} req Cloud Function request context.
* @param {Object} res Cloud Function response context.
*/
exports.corsEnabledFunction = (req, res) => {
// Set CORS headers for preflight requests
// and caches preflight response for 3600s

res.set('Access-Control-Allow-Origin', 'your-allowed-origin');
// Allows GETs from your specified origin with the Content-Type header

if (req.method === 'OPTIONS') {
// Send response to OPTIONS requests
res.set('Access-Control-Allow-Methods', 'GET');
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
} else {
res.send('Hello World!');
}
};

Alternatively, you can use a third-party library to handle CORS for you.

You may also refer to this documentation for more information.

Firebase Callable Cloud Function CORS Error

Temp fix:

In the cloud console functions page, select a function to show the info panel. In the permissions tab, select ADD MEMBER. In the new members field, type allUsers. In the roles drop down, select cloud functions, then cloud functions invoker, and save.

It actually sort of makes sense for a function to have restricted permissions when it's first created, however I'm used to the default permissions being present, so it's a bug (or new feature) that definitely threw me off. Of course this doesn't fix the underlying problem, but hope it helps.

Firebase callable cloud function CORS error, but allUsers is already set as invoker

onCall functions behaviour differently than onRequest functions with regards to the cors, for example you need to ensure you're calling it from the same region. See this post

Example:

const addAdmin = functions('europe-west2').httpsCallable('makeAdminUser')
addAdmin({ email: 'x@x.com'}).then( res => { console.log( res ) })

The cors issue can also occur if your function is erroring, worth checking in your local emmulator.

Avoid CORS preflight for Firebase callable function

After combing through Firebase docs and JS SDK source I've decided this is not possible without using/overriding private APIs.

The solution I've used is to replicate the JS SDK code but specifying a URL that goes via Firebase Hosting so it's on the same domain as my app.

Same Cloud Function, same app code, no CORS preflight /p>


  1. Create a normal Firebase Callable Cloud Function
  2. Add a rewrite to firebase.json
{
...
"hosting": {
...
"rewrites": [
{
"source": "myFunction",
"function": "myFunction"
}
]
}
}

  1. Instead of calling it with firebase.functions().httpsCallable('myFunction') send a POST request to your own new URL
const token = await firebase.auth().currentUser.getIdToken()
const response = await fetch(
'https://myapp.web.app/myFunction',
{
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token
},
method: 'post',
body: JSON.stringify({ data })
}
)

Now the URL is within your domain so no CORS issues

Firebase Functions httpsCallable localhost test CORS error

Hard to tell from the given code why your request violates Cross-Origin Resource Sharing. Take a read on this matter and figure out what is it that you include (whether by intention or not) in your HTTP request that causes it. Headers maybe?

In the meantime, you can enable CORS in Firebase Functions with require("cors"):

const cors = require("cors")({origin: true});
const functions = require('firebase-functions');
const admin = require('firebase-admin');

admin.initializeApp();

exports.hello = functions.https.onCall((data, context) => {
cors(data, context, () => {
console.log(data.text);
res.send("123");
}
});

Regarding local testing of Firebase Functions, you can do so by using this command:

firebase serve --only functions


Local testing of Firebase Hosting / Functions

Enabling CORS in Firebase Functions.



Related Topics



Leave a reply



Submit