Reputation: 1352
I have a pretty simple use-case for a front facing website. It has a contact form and the details of the contact form need to be saved to the firebase database for further processing. The website is built using NextJS
. I understand that the api
functionality of NextJS
is not usable using Firebase Hosting so as a result, I'm inclined to use Cloud Functions to set up a HTTP endpoint that accepts form data as a POST request and save it to the realtime database / Firestore.
However, I'm unable to figure out a way to secure this endpoint. How do I prevent a normal user from extracting the endpoint URL from website source code and sending multiple requests to that URL? Can I keep this endpoint responsive only for that particular domain? Or how do I resolve this?
Alternatively, I could use the Firebase SDK directly in-app and save the data to the database but that would require me to keep the contacts
collection as public for anyone to read/write, which is again a security risk.
What would be a better way to solve this issue whilst keeping the security intact? Note that since its a public website, I cannot have authenticated users with Firebase.
Upvotes: 0
Views: 963
Reputation: 7388
Both of your options can work.
Using the rest api with clloud functions you could integrate the Google Captcha.
Using directly the database you can write the database rules in a way that everyone can only add a new contact and can't read or edit it. This is still less secure because someone could fill up your database. But with a quite good field validation and duplicate restriction that would be a "lesser" problem.
Here is how we did it with our website:
The cloud functions that handle the captcha
and contact POST
:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
const rp = require("request-promise");
const nodemailer = require("nodemailer");
const gmailEmail = encodeURIComponent(functions.config().gmail.email);
const gmailPassword = encodeURIComponent(functions.config().gmail.password);
const mailTransport = nodemailer.createTransport(
`smtps://${gmailEmail}:${gmailPassword}@smtp.gmail.com`
);
exports.checkRecaptcha = functions
.region("europe-west1")
.https.onRequest((req, res) => {
const response = req.query.response;
console.log("recaptcha response", response);
rp({
uri: "https://recaptcha.google.com/recaptcha/api/siteverify",
method: "POST",
formData: {
secret: "TOP_SECRET",
response: response,
},
json: true,
})
.then((result) => {
console.log("recaptcha result", result);
if (result.success) {
res.send("You're good to go, human.");
} else {
res.send("Recaptcha verification failed. Are you a robot?");
}
})
.catch((reason) => {
console.log("Recaptcha request failure", reason);
res.send("Recaptcha request failed.");
});
});
exports.triggerEmail = functions
.region("europe-west1")
.https.onRequest((req, res) => {
if (req.method !== "POST") {
res.status(400).send("Please send a POST request");
return;
}
const values = JSON.parse(req.body);
const email = "[email protected]";
const bcc = "[email protected]";
const mailOptions = {
subject: "Kontakt von Website",
text: `Datum: ${values.dateTime}\n
Name: ${`${values.gender} ${values.firstname} ${values.lastname}`}
Firmenname: ${values.company ? values.company : ""}
Strasse: ${values.street ? values.street : ""}
Ort: ${values.place ? values.place : ""}
Land: ${values.country ? values.country : ""}
PLZ: ${values.zip ? values.zip : ""}
E-Mail: ${values.email ? values.email : ""}\n\n
${values.text ? values.text : ""}`,
to: email,
bcc: bcc,
};
console.log(req.headers.origin);
if (
req.headers.origin == "https://www.your_company.com" ||
req.headers.origin == "http://localhost:3000"
) {
res.setHeader("Access-Control-Allow-Origin", req.headers.origin);
}
return mailTransport.sendMail(mailOptions).then(() => {
res.status(200).send("OK");
});
});
The Captcha in our side usinge react:
<ReCAPTCHA
ref='recaptcha'
sitekey='SECRET_KEY'
onChange={response => this.setState({ response: response })}
/>
And the contact POST
call:
fetch('https://URL_TO_YOUR_FUNCTION_THAT_SENDS_THE_EMAIL', {
method: 'POST',
body: JSON.stringify({
dateTime: new Date().toString(),
gender: this.state.gender,
firstname: this.state.firstname,
lastname: this.state.lastname,
company: this.state.company,
street: this.state.street,
place: this.state.place,
country: this.state.country,
zip: this.state.zip,
email: this.state.email,
text: this.state.text,
isLogistik: isLogistik
})
})
Make sure that your Captcha settings are setup to us the second cloud function for verification.
Upvotes: 0
Reputation: 50840
That is not possible. Users can see all the URLs they are making calls to in the networks tab. Your serverless functions must be ready to handle spam (like reject malicious or badly formatted requests), though you will still be charged for the CPU usage. That is one of the biggest cons of being serverless. But you will be saving a lot of time setting up servers and all that hassle.
The best you can do is enable CORS which still won't prevent spam but will reject requests after the pre-flight request. Though only browsers follow CORS and API clients like postman or insomnia don't
This cannot be considered as a security threat as it all depends on your code's logic, however you'll be charged for the usage and that's the risk involved. There are services like Cloudflare API Shield but again, Firebase has it's own SDK so that can be bypass somehow.
Coming to the reCaptcha case which involves verifying the reCaptcha token on the backend, you may get rid of bots to some extents. But if someone just keeps spamming your server without valid token, your functions are still going to charge you for time taken for validating the token.
Please let me know if you have any more questions.
Upvotes: 1