Solving CORS using a firebase function - node.js

As you know we can't fetch a url on a client side due to CORS.
I am trying to create a function, that will fetch the file for me with the right headers as an agent, and will return it to the client.
So, on Firebase Function :
exports.urlToBlob = functions.https.onRequest((request,response) => {
cors(request, response, () => {
var url = "https://..."; //request.query.url;
fetch (
url,
{
method: 'GET',
headers: { 'Accept': '*/*' }
}
)
.then ((res) => {console.log(res);return res;})
.catch((err) => response.status(400).send(err))
});
});
I can see that it will access the url and get a respond on shell simulator, but not on browser, which will return nothing.
The client suppose to access it with :
var url = "https://us-central1-myproject-6xxxx.cloudfunctions.net/urlToBlob";
$.get(
url,
{url :imgurl, platform : 'xxx'},
function(data) {
console.log(data);
}
);
I know i am doing it wrong, but i have to find a way to use the function to solve the CORS.

Have you tried use cors lib from nodejs.
You can make a configuration for cors to allow cors in your instance.
First install cors lib:
npm install cors --save
After that you can configure cors in you main module application.
As an example:
const cors = require('cors')
//cors config
app.use(cors({
origin: ['https://example.com'] //<-- Here you put the domain you want to call your api
methods: "GET, POST, PUT, DELETE",
optionsSuccessStatus:200,
credentials: true
}))
In your code:
.then ((res) => {console.log(res);return res;})
You should change to:
.then ((res) => {
console.log(res);
//return res;
response.status(200).send(res);
})

Related

Cookies do not stored when set it with axios POST method

I'm trying to write and read cookies and falling into a problem below.
This is my basic server side:
server.js
const app = express();
app.use(cors());
app.use(cookieParser());
import routes from '...';
app.use("/foo", routes);
app.listen(8888);
routes.js
const routes = express.Router();
routes.post('/', (req, res) => {
res.cookie("myFoo", "abcd");
res.send("Cookie added");
}
});
routes.get('/', (req, res) => {
res.send(req.cookies.myFoo);
}
});
export default routes;
And my client side at "http://localhost:3000".
I do two HTTP request
POST http://localhost:8888/foo
GET http://localhost:8888/foo
And get the response exactly what I expected abcd. Also, the cookie exists in the browser tab Application > Cookies too.
The problem cases when axios is used in the client.
const api = axios.create({
baseURL: "http://localhost:8888/foo"
});
async function setCookie(object) {
return api.post("/", object)
.then((res) => {
return res;
});
}
function getCookie() {
return api.get("/")
.then((res) => {
return res;
});
}
setCookie({})
.then((res) => {
getCookie();
})
The api.post() run usually and the header response Set-Cookie is correct. But cookies in the browser tab Application > Cookies are empty. Also, api.get() get the undefined.
I did try to move res.cookie() or the set cookie job in server side to GET route it WORKS on both HTTP and axios
routes.get('/', (req, res) => {
res.cookie("myFoo", "abcd");
});
tldr: Set cookie in HTTP POST method work fine but when client use axios to call so it causes problems.
Can you show me why this happened? And which code part went wrong that caused me into this?
Cookies are only used in cross-origin Ajax requests when:
The client asks to use them
The server grants permission to use them cross origin
So you need to change the client side code to ask for them:
const api = axios.create({
baseURL: 'http://localhost:8888/',
withCredentials: true,
});
And the server code to grant permission (note that you can't use credentials at the same time as the wildcard for origins).
app.use(cors({
origin: 'http://localhost:3000',
credentials: true
}));

Firebase Cloud Function does not execute when called from web client served by Netlify

I have been trying to invoke a Firebase Cloud Function without success.
The sucessfully deployed function is:
functions.logger.info("Hello logs!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // this logs
exports.createAuthToken = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", { structuredData: true }); // this doesn't log
response.send("Hello from Firebase!");
});
The first line of the code shows up in the logs, but the third line doesn't when called from an external web client. When I test / run the function from the Google Cloud console, both logs show up.
In the web client, I get the error message:
Access to fetch at 'https://us-central1-my-project.cloudfunctions.net/create-auth-aoken?ot-auth-code=xyz' from origin 'https://my-client-url' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
So I resolve the cors issue as the documentation suggests like this:
const cors = require('cors')({
origin: true,
});
functions.logger.info("Hello logs!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // this logs
exports.createAuthToken = functions.https.onRequest((request, response) => {
cors(request, response, () => {
functions.logger.info("Hello logs >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>!", { structuredData: true }); // this still doesn't
response.send("Hello from Firebase!");
});
});
I am calling the function like this:
https://us-central1-my-project.cloudfunctions.net/create-auth-token?ot-auth-code=${code}&id-token=${token}
I have tried resolving the cors problem in every way I can possibly find, but it will not work.
How should I configure this exactly to make it work?
This could be addressed in several ways, one is by sending your request to a proxy server. Your request would look like this:
https://cors-anywhere.herokuapp.com/https://joke-api-strict-cors.appspot.com/jokes/random
This is an easy solution and works for dev environmeent as it is implemented on browser-to-server communication. The downsides are that your request may take a while to receive a response as high latency makes it appear to be slow, and may not be not ideal for production environment.
Another option is to try using CORS with Express. Check the Node.js code below:
const express = require('express');
const request = require('request');
const app = express();
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*');
next();
});
app.get('/jokes/random', (req, res) => {
request(
{ url: 'https://joke-api-strict-cors.appspot.com/jokes/random' },
(error, response, body) => {
if (error || response.statusCode !== 200) {
return res.status(500).json({ type: 'error', message: err.message });
}
res.json(JSON.parse(body));
}
)
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`listening on ${PORT}`));
This one would act as a middleware to apply Access-Control-Allow-Origin: * header to every request from the server. Same-origin policy will not step in to block the request even though the domains are different.
You may check the full documentations on how to fix CORS error:
3 Ways to Fix the CORS Error — and How the Access-Control-Allow-Origin Header Works
Resolving CORS policy error
What is CORS (Cross-Origin Resource Sharing) and How to Fix the CORS Error?
CORS full documentation
It certainly is possible; I use this solution for firebase which I rolled myself to simply respond to CORS requests:
function MakeCORSRequest(func) {
return functions.https.onRequest((req, res) => {
res.set('Access-Control-Allow-Origin', '*');
if (req.method === 'OPTIONS') {
// Send response to OPTIONS requests
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Headers', 'Content-Type');
res.set('Access-Control-Max-Age', '3600');
res.status(204).send('');
} else {
return func(req, res);
}
});
}
All it does is intercept CORS OPTIONS request and respond with 'Yes you can' - other requests (such as GET/POST) it just passes forward to the given function. It can be used to wrap your endpoint and respond correctly to CORS calls coming from the browser.
So the actual end point is expressed like this:
exports.myFunction = MakeCORSRequest(async (req, response) => {
//Do your stuff here
})
I wasn't able to solve the CORS issue but I did manage to make the cloud function work eventually by using functions.https.onCall instead of functions.https.onRequest. I found this answer https://stackoverflow.com/a/51477892/11584105 and followed these official docs: https://firebase.google.com/docs/functions/callable#web-version-9

The "x-auth-token" header in Router.all() returns undefined

I am currently building an application using React for frontend and Nodejs for backend powered by Express.js.
I'm using jsonwebtoken for security method and applying a middleware called auth.js to authorize the request on every endpoints that starts with /rest, here is the code for auth.js:
const token = req.header('x-auth-token');
console.log(token); // Get the token from 'x-auth-token' header
if (!token) {
return res.status(400).json({ msg: 'Authorization denied. ' });
}
try {
// validate the token
next();
} catch (e) {
return res.status(401).json({ msg: 'Invalid token. '})
}
and the routing for /rest/* endpoints:
router.all("/", auth, (req, res) => {
// some codes
});
the request:
fetch(url + "/rest", {
method: "GET",
mode: "cors",
headers: {
"x-auth-token" : "this is the token" // define the header
"Accept" : "application/json",
"Content-Type" : "application/json",
}
});
The router.all() mechanism works fine, I'm able to access every /res routes with all methods. The problem is, the value of the x-auth-token header in the auth.js middleware always gives "undefined". when I change the routing to route.get() or route.post() etc.., that value of the x-auth-token returns the token from client correctly.
Am I missing something with the work around this router.all()? Thank you all.
EDIT: here's my cors middleware
module.exports = cors = (req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', {domain});
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, PATCH, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type , Accept, x-auth-token');
res.setHeader('Access-Control-Allow-Credentials', true);
next();
}
SOLVED:
So turns out, the reason why my x-auth-token header is missing in the req is because of the Pre-flight request mentioned by #Marcos Casagrande.
Now, what I went with is installing the CORS package and configured it following the Express documents and ended up with the following snippet in the server.js file since I want that cors configuration to be applied on every endpoints:
let cors = require("cors");
let whitelist = [{domains}]
let corsOptions = {
origin: (origin, callback) => {
if (whitelist.indexOf(origin) !== -1 || !origin) {
callback(null, true)
} else {
callback(new Error('Not allowed by CORS'))
}
}
}
app.use(cors(corsOptions));
Thank you all for helping me out.
When using router.all, OPTIONS will need to be handled, and x-auth-token won't be available there.
When you issue a request from the browser, an OPTIONS request will be issued first by the browser.
If you put:
console.log(req.method, req.headers);
You'll see: OPTIONS & x-auth-token missing. After OPTIONS has been handled correctly, the browser will issue the GET request, where the header will be present.
So you can handle it your self, and set the right Access-Control-Allow-Origin header if issuing a CORS request, or use cors package.
const app = require('express')();
const cors = require('cors');
app.use(cors());
// ...
router.all("/", auth, (req, res) => {
// No OPTIONS here, was already handled by `cors`
});
If you're not issuing a CORS request just use this in your auth middleware:
if(req.method === 'OPTIONS')
return res.send(); // 200
or handle options first
router.options('*', (req, res) => res.send());
router.all("/", auth, (req, res) => {
// some codes
});
Read more about Preflight Request
I see you are trying to access header value from req.header and not from req.headers, and you have a "Content-Type" : "application/json", in you GET request which will make a OPTION request anyway.
Your client app is making a cross origin request and you nodejs server must handle it. You can use the cors to solve this.
You can send token in any custom header, but a better practice/standardisation is to use Authorization header.

After deploying React app to Heroku from dev env, Fetch requests broken - all GET requests returning index.html - others are 404

I have just deployed my Create-React-App project to Heroku. In development, I was running two separate ports - the local HTML + JS were being served from the React WebpackDevServer using the npm/yarn start scripts on Port 3000. The Backend was Express + NodeJS running on port 3001. I configured all fetch requests to use mode:'cors' and provided a handler on the API so to avoid CORS errors.
A typical fetch request would look like this:
When I deployed to Heroku, everything is now kept together on a single Dyno, and the Express app serves the React files (bundle + index.html) and also handles backend routing logic.
Here is a sample of my API code so-far:
const express = require('express');
const mongoose = require('mongoose');
const path = require('path');
const bodyParser = require('body-parser');
const config = require('./models/config');
require('dotenv').config()
const app = express();
const server = require('http').createServer(app);
const storeItems= require('./controllers/storeItems')
const authorize= require('./controllers/authorize')
const router = express.Router();
mongoose.Promise = global.Promise;
mongoose.connect(`mongodb://${process.env.MLABS_USER}:${process.env.MLABS_PW}#ds113000.mlab.com:13000/omninova`, { useMongoClient: true });
app.use(bodyParser.json());
// Middleware to handle CORS in development:
app.use('/*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, x-access-token, x-user-pathway, x-mongo-key, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
next();
});
app.use(express.static(path.join(__dirname, 'client/build')));
router.route('/api/storeItem/')
.get(storeItems.getAllStoreItems)
.post(storeItems.createNewStoreItem);
router.route('/authorize')
.post(authorize.login);
// Catch-All Handler should send Client index.html for any request that does not match previous routes
router.route('*')
.get((req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
app.use('/', router);
server.listen(config.port);
module.exports = app;
I'm having some issues, ALL of my get requests are returning my index.html page with the following Error: Unexpected token < in JSON at position 0
I have the following Fetch Request:
return fetch(`/api/storeItem`, {
headers:{
'Content-Type': 'application/json',
},
method: 'GET',
mode: 'no-cors',
})
.then(response => response.ok ? response.json() : Promise.reject(response))
.then(json => {
dispatch(receiveItems(json))
})
.catch(err => console.log(err))
This is failing, because instead of triggering the Express Middleware that should be running storeItems.getAllStoreItems on the backend, It's passing that route and triggering the catch-all handler, which I use to serve the index.html upon initial request:
router.route('*')
.get((req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
Another confusion is that this following fetch request returns a 404, even though the /authorize route is expecting a POST request in the API code:
export function attemptLogIn(credentials) {
return dispatch => {
return fetch('/authorize', {
headers:{
'Content-Type': 'application/json'
},
method: 'POST',
mode: 'no-cors'
body: JSON.stringify(credentials)
})
.then(response => response.ok ? response.json() : Promise.reject(response.statusText))
.then(json => {
dispatch(routeUserAfterLogin(json.accountType))
})
.catch(err => dispatch(authFail(err.message)))
}
}
Any help with this would be highly appreciated. I assume I am doing something wrong with the Express Router, since the authorize route is just not being picked up.
I followed the instructions in this blog post to help me set up my new project: https://daveceddia.com/deploy-react-express-app-heroku/
Edit: This is Fetch code from my Development branch. This successfully logs the user in, without returning a 404. However, I do not use the catch-all handler at all, or the express.static middleware:
return fetch('http://localhost:3001/authorize', {
headers:{
'Content-Type': 'application/json'
},
method: 'POST',
mode: 'cors',
body: JSON.stringify(credentials)
})
Edit: I just changed the URL which points to the bundle.js to
app.use(express.static(path.join(__dirname, '/../../build')));
I'm not sure how I was even sending out the HTML before, since that's the actual location of build files..I'm not sure how they were being found before.
Edit2: Found my problem, I left in the start script for my React project (which actually started the webpack dev server...)
For the first part
Error: Unexpected token < in JSON at position 0
My guess is that without the dev server, express can't handle the bundle download automatically. So, when your index.html hits /path/to/bundle.js, it falls under the wildcard ("*") route, which returns the HTML itself. Your browser then tries to parse it as JS, but it can't since it's HTML, hence the error.
I would try something like:
app.get("path/to/bundle.js", (req, res) => {
res.sendFile("path/to/bundle.js");
});
Your server is not sending json response but HTML content. Change response to res.text() and log it. https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
router.route('*')
.get((req, res) => {
res.sendFile(path.join(__dirname+'/client/build/index.html'));
});
This should fix the problem. Instead of "*" there should be "/". Now my GET request works like the POST on Heroku.
https://stackoverflow.com/a/37878281/12839684

POST request not returning Allow-Origin header

I'm trying to make a POST request on a AWS lambda that has cors enabled and I using the cors package in the API.
Then I make a POST request with fetch but I get this error:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
The cors package was not supposed to take tare of this or I need to do some additional configuration?
api.js
import { json, urlencoded } from 'body-parser'
import express from 'express'
import cors from 'cors'
import recipe from './routes/recipe'
import login from './routes/login'
import unit from './routes/unit'
const app = express()
app.use(cors()) // enabling cors
app.use(json())
app.use(urlencoded({ extended: true }))
app.use('/recipes', recipe)
app.use('/login', login)
app.use('/units', unit)
request.js
const params = { 'POST', body, mode: 'cors' }
return await (await fetch(baseURL + endpoint, params)).json()
recipe.js
import { Router } from 'express'
const router = Router()
router.post('/', async ({ body }, res) => {
// save recipe in database
res.json(recipeId)
})
export default router
full source code - api: https://github.com/EliasBrothers/zanzas-recipes-api/tree/beta-develop
frontend - https://github.com/EliasBrothers/zanzas-recipes-web/tree/develop
I made it work by updating the packages from the project and changing the request
const stringifiedBody = JSON.stringify(body) // added
const params = {
method,
body: stringifiedBody,
mode: 'cors',
headers: { // added
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}
I'm not sure if this is a copying error, you didn't specify the fetch method POST in right way. Sometimes a Not Found page will also be recognized as CORS problem. Have you tried to normalize your fetch parameters?
params = {
method: 'POST',
body: body,
mode:'cors'
}

Resources