Firebase Functions HTTPS 403 Forbidden

12,166

Solution 1

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.

Solution 2

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

Solution 3

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.

That's it, now test your api.

Solution 4

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);

Solution 5

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: '[email protected]',
      pass: 'Gietvloermakers2020!'
    }
  });

  var mailOptions = {
    from: '[email protected]',
    to: '[email protected]',
    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).

Share:
12,166
Gijs van Beusekom
Author by

Gijs van Beusekom

Updated on June 06, 2022

Comments

  • Gijs van Beusekom
    Gijs van Beusekom about 2 years

    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: '[email protected]',
          pass: 'Gietvloermakers2020!'
        }
      });
    
      var mailOptions = {
        from: '[email protected]',
        to: '[email protected]',
        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>
            <a href="bestellen.html"><p id="andere-kleur">Bestelling aanpassen</p></a>
        </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.