I'm still having problems with CORS when using Firebase HTTP functions.
Here is my web console error:
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://localhost:3000' is therefore not allowed access. The
response had HTTP status code 404.
Here is my function:
const cors = require('cors')({ origin: true });
const express = require('express');
const functions = require('firebase-functions');
const app = express();
const validate_user = require('./validate_user_id_token.js');
const charge_card = async(req, res) => {
// ...
}
app.use(cors);
app.use(validate_user);
app.use(charge_card);
exports.foo = functions.https.onRequest(app);
I think I've read over every single Firebse CORS question. I also have a near replica of the sample provided here.
Please help :)
Edit:
The URL I am calling is correct (used texdiff.com just to be sure, and functions logs are showing it as executed but returning 404). For reasons unknown, a 404 is returned regardless. Perhaps that is CORS mechanism?
Update:
I got things working without using express by putting using cors() in my onRequest handler:
exports = module.exports = functions.https.onRequest(async(req, res) => {
cors(req, res, () => {});
await charge_card(req, res);
});
Not ideal, but it works for now :/
According to the Firebase documentation, there are a couple of references to CORS configuration:
Using CORS:
You can enable the use of CORS by calling it within the function, just like you did in your update to the question:
// Usage of the `cors` express middleware
return cors(req, res, () => {
// TO-DO
});
Also if you have an already existing Express app, you can then enable CORS by doing:
const app = express();
app.use(cors({ origin: true }));
This is what you had already done on the first step, but there's the difference in the { origin: true } definition, so maybe that is related.
In any case, as per the documentation it looks like it is indeed fine to add the cors within the request.
Related
This question already has answers here:
NextJs CORS issue
(10 answers)
Closed 8 months ago.
I am trying to deploy a firebase function and call the function from a nextjs app. The function works when it runs on firebase emulator, and when it is deployed I am able to call the function from postman. However, when deployed and I try to call using fetch I get an error. I have also tried to deploy the website to call from a different url but still get the same error.
Here is the calling function:
export async function getArticle(articleURL) {
const response = await fetch(articleURL);
const json = await response.json();
return json.result;
}
Here is the firebase function that I am trying to call:
const cors = require('cors')({origin: true});
exports.getArticle = functions
.runWith({
timeoutSeconds: 120,
memory: "1GB",
})
.https.onRequest(async (req, response) => {
cors(req, response, async() => {
try {
{code}
response.status(200).json({ result: 'test' });
}
catch (e) {
response.status(400).json({ result: "error", message: e.message });
}finally {
{code}
}
})
});
I have also tried adding headers such as:
response.set('Access-Control-Allow-Origin', "*");
response.set('Access-Control-Allow-Headers', "*");
but nothing seems to work. I have tried for hours but nothing works. The error I get is:
[Error] Cross-origin redirection to (url) denied by Cross-Origin Resource Sharing policy: Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin. Status code: 301
[Error] Fetch API cannot load (url) due to access control checks.
[Error] Failed to load resource: Cross-origin redirection to (url) denied by Cross-Origin Resource Sharing policy: Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin. Status code: 301
[Error] Unhandled Promise Rejection: TypeError: Load failed
Lastly, I have also tried adding parameters to my fetch call such as :
fetch(URL, {
mode: 'cors',
headers: {
'Access-Control-Allow-Origin':'*'
}
})
Overall, nothing seems to work and don't know what else to try. Any help would be greatly appreciated.
The problem was not with the firebase function but with the api call on nextjs. I was importing the function and making the call from a component. Instead I found the solution on this post: NextJs CORS issue.
The solution that worked for me was by chispitaos. I changed my getArticle function to the api format and fetched the internal api which then fetched the firebase function. Here was my new function:
export default async function handler(req, res) {
try {
const response = await fetch(`any url`);
const json = await response.json();
console.log(json);
res.status(200).send(json);
} catch (error) {
console.error(error)
return res.status(error.status || 500).end(error.message)
}
}
and here was how I called it :
const newData = await fetch('/api/getArticle');
This fixed the issue for me.
I'm building an app for shopify and need to add the GDPR webhooks. My back end is handled using next.js and I'm writing a webhook handler to verify them. The docs havent been very helpful because they dont show how to do it with node. This is my verification function.
export function verifiedShopifyWebhookHandler(
next: (req, res, body) => Promise
): NextApiHandler {
return async (req, res) => {
const hmacHeader = req.headers['x-shopify-hmac-sha256'];
const rawBody = await getRawBody(req);
const digest = crypto.createHmac('sha256', process.env.SHOPIFY_API_SECRET).update(rawBody).digest('base64');
if (digest === hmacHeader) {
return next(req, res, rawBody);
}
const webhookId = req.headers['x-shopify-webhook-id'];
return res.status(401).end();
};
}
But I get this Error: error - InternalServerError: stream is not readable
I think it has to do with now Next.js parses the incoming requests before they are sent to my api. Any ideas?
I discovered the answer. Next.js was pre parsing the body in the context which made it so that I couldn't use the raw body parser to parse it. By setting this:
export const config = {
api: {
bodyParser: false
}
};
above the api function in the api file it prevented next from parsing it and causing the issue. I found the answer because people had the same issue integrating swipe and using the bodyParser.
I've deployed a small HTTP endpoint via Google Cloud Run. It is working fine when I turn off the authentication.
I now want to turn it on so that it is only callable by my Firebase Cloud Function. If I understand it right, I just have to add the correct service account mail address in the IAM settings of the Cloud Run as "Cloud Run invoker".
But which address is the correct one?
I've tried all addresses that I have found in Firebase Console -> Project Settings -> Service Accounts.
I think you can check the specific firebase function. In the UI, the service account used should be listed.
By default, GCF functions all use <project_id>#appspot.gserviceaccount.com
Thanks to #AhmetB - Google and #whlee's answer I got it working. Basically it is enough adding an Authorization Bearer token to the request, which you can get from a special endpoint: https://cloud.google.com/run/docs/authenticating/service-to-service#nodejs
Then you just have to add the service account of the function to the IAM list of the Cloud Run container: <project_id>#appspot.gserviceaccount.com
The nodejs example is using the deprecated request library, so here is my version using axios:
const getOAuthToken = async (receivingServiceURL: string): Promise<string> => {
// Set up metadata server request
const metadataServerTokenURL = 'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience=';
const uri = metadataServerTokenURL + receivingServiceURL;
const options = {
headers: {
'Metadata-Flavor': 'Google'
}
};
return axios.get(uri, options)
.then((res) => res.data)
.catch((error) => Promise.reject(error));
}
Then you can just use the token in the actual request:
const url = `...`;
const token = await getOAuthToken(url);
axios.post(url, formData, {
headers: {
Authorization: `Bearer ${token}`,
}
}).then(...).catch(...);
#luhu 's answer was really helpful. I'd like to add just one note for those whose are willing to test with the emulators locally first. The metadata server (which is actually http://metadata.google.internal now) as they state
does not work outside of Google Cloud, including from your local machine.
As a workarund, you can use the google-auth-library and then get the token directly if you prefer sticking with axios. Remember to set the GOOGLE_APPLICATION_CREDENTIALS env variable pointing to a service account secret first as it's the only way to make it work (I've tested setting the credential field during admin.initializeApp() but didn't seem to like it).
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const url_origin = '....'
const client = await auth.getIdTokenClient(url_origin);
const token = (await client.getRequestHeaders()).Authorization;
const url = '....'
const response = await axios.get(
url,
{
headers: {
Authorization: token,
},
}
);
I'm building a hybrid mobile app with Firebase as my backend. I want to let users post on a wall any message they want without authentication, but I feel concerned about spam possibilities. I mean, if users don't have to be authenticated to be able to post, my security rules are basically empty and anyone who gets the endpoint can post an infinite amount of content. And I don't see what I could do against it.
So I know about anonymous auth, but I'm not sure if it really fix the issue. The endpoint remains open, after all, just behind the necessity to call a method before. It adds a little complexity but not much, I think.
What I wonder is if there is a possibility to check for the call origin, to make sure it comes from my app and nothing else. Or, if you have another idea to get this more secure, I'm open to everything. Thanks!
You can accomplish this using a combination of recaptcha on the client, and firebase cloud functions on the backend.
You send the message you want to add to the store along with the captcha to the cloud function. In the cloud function, we first verify the captcha. If this one is ok, we add the message to the store. This works, because when adding items to the store via a cloud function, firebase authentication rules are ignored.
Here's an example cloud function:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const rp = require('request-promise')
const cors = require('cors')({
origin: true,
});
admin.initializeApp();
exports.createUser = functions.https.onRequest(function (req, res) {
cors(req, res, () => {
// the body is a json of form {message: Message, captcha: string}
const body = req.body;
// here we verify whether the captcha is ok. We need a remote server for
// for this so you might need a paid plan
rp({
uri: 'https://recaptcha.google.com/recaptcha/api/siteverify',
method: 'POST',
formData: {
secret: '<SECRET>',
response: body.captcha
},
json: true
}).then(result => {
if (result.success) {
// the captcha is ok! we can now send the message to the store
admin.firestore()
.collection('messages')
.add(body.message)
.then(writeResult => {
res.json({result: `Message with ID: ${writeResult.id} added.`});
});
} else {
res.send({success: false, msg: "Recaptcha verification failed."})
}
}).catch(reason => {
res.send({success: false, msg: "Recaptcha request failed."})
})
});
})
And here's some more info: https://firebase.googleblog.com/2017/08/guard-your-web-content-from-abuse-with.html
So I would like to do something like:
app.On_All_Incoming_Request(function(req, res){
console.log('request received from a client.');
});
the current app.all() requires a path, and if I give for example this / then it only works when I'm on the homepage, so it's not really all..
In plain node.js it is as simple as writing anything after we create the http server, and before we do the page routing.
So how to do this with express, and what is the best way to do it?
Express is based on the Connect middleware.
The routing capabilities of Express are provided by the router of your app and you are free to add your own middlewares to your application.
var app = express.createServer();
// Your own super cool function
var logger = function(req, res, next) {
console.log("GOT REQUEST !");
next(); // Passing the request to the next handler in the stack.
}
app.configure(function(){
app.use(logger); // Here you add your logger to the stack.
app.use(app.router); // The Express routes handler.
});
app.get('/', function(req, res){
res.send('Hello World');
});
app.listen(3000);
It's that simple.
(PS : If you just want some logging you might consider using the logger provided by Connect)
You should do this:
app.all("*", (req, res, next) => {
console.log(req); // do anything you want here
next();
});
You can achieve it by introducing a middleware function.
app.use(your_function) can be of help. app.use with accept a function that will get executed on every request logged to your server.
Example:
app.use((req, res, next) => {
console.log("req received from client");
next(); // this will invoke next middleware function
});
Express supports wildcards in route paths. So app.all('*', function(req, res) {}) is one way to go.
But that's just for route handlers. The difference is that an Express route handler is expected to send a response, and, if it doesn't, Express will never send a response. If you want to do something without explicitly sending a response, like check for a header, you should use Express middleware. app.use(function(req, res, next) { doStuff(); next(); }