I built a Firebase HTTP Event function with Node and Express. The function is working, but when I invoke the function on the client side I get 403 Forbidden. The first time I invoked the function I was asked to sign in with a Google account. I signed in with the same account I use for Firebase, but when I invoked the function I got:
Screenshot of 403 error
I looked at the use roles on Google cloud platform and the permission to invoke the function is set to allUsers. I signed out and back in again in the Firebase CLI.
Here is the index.js in the functions folder:
const functions = require('firebase-functions');
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const port = process.env.port || 5600
const nodemailer = require('nodemailer');
app.use(express.static('Public'));
app.use(bodyParser.urlencoded({ extended: true }));
const urlencodedParser = bodyParser.urlencoded({extended: true});
app.post("/api/user", urlencodedParser, (req, res) => {
res.sendFile('../Public/bedankt.html', {root: __dirname})
const persGegevens = req.body
const string = JSON.stringify(persGegevens, (key, value) => {
if (typeof value === "string"){
return value.toUpperCase();
} else {
return value
}
}, 1);
var transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'gietvloermakers#gmail.com',
pass: 'Gietvloermakers2020!'
}
});
var mailOptions = {
from: 'gietvloermakers#gmail.com',
to: 'gvbeusekom84#hotmail.com',
subject: 'Nieuwe bestelling op Gietvloermakers',
html: string
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
} else {
console.log('Email sent: ' + info.response);
}
});
});
exports.app1 = functions.https.onRequest(app);
app.listen(port);
console.log(port);
Here is the html:
<form id="controlleer-form" action="/api/user" method="post" enctype="application/x-www-form-urlencoded">
<div class="controleer-div">
<h2>Uw bestelling</h2>
<p>Aantal m2</p>
<input class="controle-input" type="text" name="aantalM2" id="aantalM2" readonly>
<p>Kleur</p>
<input class="controle-input" type="text" name="kleur" id="kleur" readonly>
<p>Assistentie</p>
<input class="controle-input" type="text" name="assistentie" id="assistentie" readonly>
<p>Gereedschappen</p>
<input class="controle-input" type="text" name="gereedschappen" id="gereedschappen" readonly>
<p>Totale prijs</p>
<input class="controle-input" type="text" name="totale-prijs" id="totale-prijs" readonly>
<p id="andere-kleur">Bestelling aanpassen</p>
</div>
<div class="controleer-div">
<h2>Uw gegevens</h2>
<p>Voornaam</p>
<input type="text" name="voornaam" placeholder="Voornaam">
<p>Achternaam</p>
<input type="text" name="Achternaam" placeholder="Achternaam">
<p>Straatnaam en huisnummer</p>
<input type="text" name="Achternaam" placeholder="Straatnaam en huisnummer">
<p>Postcode</p>
<input type="text" name="Achternaam" placeholder="Postcode">
<p>Telefoonnummer</p>
<input type="tel" name="telefoonnummer" placeholder="Telefoonnummer">
<p>Emailadres</p>
<input type="email" name="email" placeholder="Emailadres"><br>
<input id="verzenden" type="submit">
</div>
</form>
Here is the firebase.json:
{
"hosting": {
"public": "Public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [{
"source": "**",
"function": "app1"
}]
}
}
I tried but I exhausted all possible solutions I've found online so far.
I encountered this recently. It turns out that as of January 15, 2020 new functions require authentication by default.
See the docs here for details.
The solution was to manually add the Cloud Functions Invoker permission to the allUsers user in the Cloud Functions page in the Google Cloud Console.
If you are getting 403 forbidden error like below
Error: Forbidden Your client does not have permission to get URL
/api/test from this server.
Please follow below steps to grant access to all users. Basically this is to allow unauthenticated clients to access your api endpoint.
Go to https://console.cloud.google.com/functions/list
Select the function to which you want to give public access
Click on PERMISSIONS
Click on ADD MEMBER
Type allUsers
Select role Cloud Functions -> Cloud Functions Invoker
Save
That's it, now test your api.
This has to do with permission access to your cloud functions http requests and cloud function events, you need to edit your cloud function IAM permission.
https://cloud.google.com/functions/docs/securing/managing-access-iam#allowing_unauthenticated_function_invocation
Had the same problem (was asked to login with my Google Account, then denied access). It turned out that functions do currently not work outside the default region. In my case, I had to make a change here:
exports.app = functions
.region('europe-west6') // does not work, delete this line
.https.onRequest(app);
Your code exports the express application as the Cloud Function app1 on this line:
exports.app1 = functions.https.onRequest(app);
In your screenshot, you have tried to access the non-existent app Cloud Function instead resulting in the 403 Forbidden response.
This means the correct URL to call from your client is
http://us-central1-gietvloermakers.cloudfunctions.net/app1/api/user
^^^^
(or you could change the name of the export to app)
Having a closer look at your source code, you should also remove the following lines. If you wanted to test your code you would instead use firebase serve.
const port = process.env.port || 5600
/* ... */
app.listen(port);
On the following lines, you also inject the body parser twice.
app.use(bodyParser.urlencoded({ extended: true })); // use this
const urlencodedParser = bodyParser.urlencoded({extended: true}); // or this, not both
app.post("/api/user", urlencodedParser, ...
In your code, you also have:
app.post("/api/user", urlencodedParser, (req, res) => {
res.sendFile('../Public/bedankt.html', {root: __dirname})
/* do some other stuff */
})
This is invalid for a Cloud Function, because as soon as the Cloud Function handler (your code) calls end(), redirect() or send(), the Cloud Function is allowed to be terminated at any time which means that your email may never be sent. To fix this you need to send the file last.
app.post("/api/user", urlencodedParser, (req, res) => {
/* do some other stuff */
res.sendFile('../Public/bedankt.html', {root: __dirname})
});
My last observation, is that the error may be caused by the folder Public not existing on the server. Based on your sendFile call, you are expecting that the folder "Public" is available to your deployed function but as it is not inside the functions folder, it will not be deployed with your code.
res.sendFile('../Public/bedankt.html', {root: __dirname})
As this file would also be accessible at your-domain.com/bedankt.html, we'll redirect to it. If you wanted to send the HTML content of this file instead, move it inside your deployed functions directory.
res.redirect('/bedankt.html')
Because you appear to be trying to use your express function behind Firebase hosting, we can trim your index.js file to the following:
const functions = require('firebase-functions');
const express = require('express');
const bodyParser = require('body-parser');
const nodemailer = require('nodemailer');
const apiApp = express();
apiApp.use(bodyParser.urlencoded({ extended: true }));
apiApp.post("/api/user", (req, res) => {
const persGegevens = req.body
const string = JSON.stringify(persGegevens, (key, value) => {
if (typeof value === "string"){
return value.toUpperCase();
} else {
return value
}
}, 1);
var transporter = nodemailer.createTransport({
service: 'gmail',
auth: {
user: 'gietvloermakers#gmail.com',
pass: 'Gietvloermakers2020!'
}
});
var mailOptions = {
from: 'gietvloermakers#gmail.com',
to: 'gvbeusekom84#hotmail.com',
subject: 'Nieuwe bestelling op Gietvloermakers',
html: string
};
transporter.sendMail(mailOptions, function(error, info){
if (error) {
console.log(error);
res.redirect('/bedankt.html?success=0');
} else {
console.log('Email sent: ' + info.response);
res.redirect('/bedankt.html?success=1');
}
});
});
// note rename to api
exports.api = functions.https.onRequest(apiApp);
which requires updating your firebase.json file to:
{
"hosting": {
"public": "Public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [{
"source": "/api/**",
"function": "api"
}]
}
}
This configuration will first attempt to find a matching file in your Public directory. If it can't find a match, it will check if the requested path starts with /api and if so, launch your Cloud Function. If it still can't find a match, it will show your 404 page (or the built in one if it doesn't exist).
I'm trying to get the request query params and url in firebase functions.
Here is the code I'm using
firebase.json
{
"hosting": {
"public": "build",
"rewrites": [{
"source": "/getCoins",
"function": "getCoins"
}]
}
}
Using "firebase-functions": "^2.3.1" in package.json
functions/index.js
'use strict';
const functions = require('firebase-functions');
exports.getCoins = functions.https.onRequest((req, res) => {
console.log(req.query); // [object Object]
console.log(req.query.repeat); // empty
console.log(req.url); // '/'
console.log(req.originalUrl); // '/'
res.sendStatus(200);
});
Started the firebase functions in my windows command prompt using firebase serve --only functions. As it starts serving data from http://localhost:5000, I'm trying to request http://localhost:5000/coins-app/us-central1/getCoins?repeat=4
I'm not getting any error in the command prompt, but could only see the commented lines from the above functions/index.js code.
You should run firebase serve and request to http://localhost:5000/getCoins?repeat=4 .
functions.https.onRequest() can't accept query string parameter directly.
I'm trying to call functions from app but it doesn't work and I'm getting the following error from the console:
index.esm.js:402 OPTIONS https://us-central1-undefined.cloudfunctions.net/addMessage 404 ()
Failed to load https://us-central1-undefined.cloudfunctions.net/addMessage: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://MYWEBADDRESS' is therefore not allowed access. The response had HTTP status code 404. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"function": "addMessage"
}
]
},
"functions": {
"predeploy": [
"npm --prefix $RESOURCE_DIR run lint"
],
"source": "functions"
}
}
index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onCall((data, context) => {
// Message text passed from the client.
const text = data.text;
// Checking attribute.
if (!(typeof text === 'string') || text.length === 0) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with ' +
'one arguments "text" containing the message text to add.');
}
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
// Saving the new message to the Realtime Database.
return admin.database().ref('/messages').push({
text: text
}).then(() => {
console.log('New Message written');
// Returning the sanitized message to the client.
return { text: sanitizedMessage };
}).catch((error) => {
// Re-throwing the error as an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('unknown', error.message, error);
});
});
my script in index.html
var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({text: "messageText"}).then(function(result) {
var message = result.data.text;
console.log(message);
});
How I initialize Firebase:
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-functions.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: "**",
authDomain: "***",
databaseURL: "***",
storageBucket: "***",
};
firebase.initializeApp(config);
var functions = firebase.functions();
</script>
I was having the same exact problem, found your question without an answer, but managed to figure it out in the end.
As #Doug Stevenson mentioned above in the comments, the problem is that the Cloud Functions URL you are seeing has undefined instead of your project ID as the last piece of the url subdomain.
The reason it is undefined is because your project ID is not part of your initial Firebase config object. Like me, you likely copied and pasted the starter snippet from Firebase for the JS SDK, but you did it before they started including the Project ID as part of it. For some reason, even though the project ID is now needed to construct the cloud functions URL, the SDK doesn't error / warn if you don't include it.
All you need to do is add the following field to your config object:
projectId: <YOUR_PROJECT_ID_HERE>
Then you should see the requests no longer 404.
I'm trying to add open graph meta tags dynamically to my index.html using firebase functions. I'm following this tutorial but because I'd like to allow users to share on their facebook I need to add open graph meta tags on-demand when user wants to share content.
So, it seems to me that running the function via rewrite in firebase.json is not best solution here and I'd like to call the function when user wants to share content to Facebook.
I try to use CORS but it seems I get the "Access-Control-Allow-Origin" error in the browser when I am sending my response back. If I redirect I have no issue. But I need to send the index.html that's been updated. How can I resolve this issue?
Here's my function:
const cors = require('cors')({ origin: true });
const fs = require('fs');
exports.handler = function(req, res) {
cors(req, res, () => {
let indexHTML = fs.readFileSync('./hosting/index.html').toString();
console.log(`#share-fb-formfeed inside cors`, indexHTML);
const ogPlaceholder = '<meta name="functions-insert-dynamic-og">';
res.set('Cache-Control', 'public, max-age=300, s-maxage=600');
res.status(200).send(indexHTML); //results in Access-Control-Allow-Origin
// res.redirect(`https://thekasis.com/feed`); //this works
});
}
Using Rewrites
I also removed cors in my function and here is my firebase.json but I'm not sure if running the Firebase function all the time would be a good idea. It would be ideal to run the function only on-demand when the user is sharing something and so I really like to be able to call the function client side on-demand.
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "build/es5-bundled",
"rewrites": [{
"source": "**",
"function": "shareContentG-shareToFb"
}]
}
}
I have setup a custom domain with Firebase Hosting (eg. myapp.domain.com).
How can one redirect (or turn off) the default Firebase Hosting URL (eg. myapp.firebaseapp.com) so that the app is only accessible from the custom domain?
You cannot turn off the subdomain. Your app will always be available on https://myapp.firebaseapp.com and whatever custom domain you've set up.
To redirect people, you can add a canonical link to your HTML:
<link rel="canonical" href="http://myapp.domain.com/" />
Read more about that in Specify your canonical on the Google Webmaster Central Blog.
You could use Firebase Functions.
Free for 125K invocations/month - https://firebase.google.com/pricing
An example using Express middleware:
// functions/index.js
const functions = require('firebase-functions');
const express = require('express');
const url = require('url');
const app = express();
// Allowed domains
let domains = ['localhost:5000', 'example.com'];
// Base URL to redirect
let baseurl = 'https://example.com/';
// Redirect middleware
app.use((req, res, next) => {
if (!domains.includes(req.headers['x-forwarded-host'])) {
return res.status(301).redirect(url.resolve(baseurl, req.path.replace(/^\/+/, "")));
}
return next();
});
// Dynamically route static html files
app.get('/', (req, res) => {
return res.sendFile('index.html', { root: './html' });
});
// ...
// 404 middleware
app.use((req, res) => {
return res.status(404).sendFile('404.html', { root: './html' });
});
// Export redirect function
exports.redirectFunc = functions.https.onRequest(app);
The exported function name must be added to rewrites in firebase.json e.g.:
{
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"function": "redirectFunc"
}
]
}
}
In addition to specifying canonical link as mentioned in Frank van Puffelen's answer. We can also add front end JavaScript code to do the actual redirect like this without disclosing default url.
if (location.hostname.indexOf('custom.url') === -1) {
location.replace("https://custom.url");
}