Cross-Origin HTTP Request originating from server-side NodeJS/Axios/JSDOM - node.js

I am using Axios to create a HTTP request to a API server in different domain.
The API server allows Cross-Origin requests from http://localhost:3000.
I have no control over the API server.
My app usually runs in http://localhost:3000 and makes requests from browser.
There's no problem up to this point. Cross-Origin requests are working fine. However, recently I want to add a unit test for those API calls. This test environment is jsdom since I'm using Jest. This raises a problem when I create HTTP request from server-side, the origin is set to http://localhost, which the server does not allow.
The request is made using Axios:
axios.post(`${API_DOMAIN}/member/account/login`, {
username,
password,
}, {
headers: {
Origin: 'http://localhost:3000'
}
})
However, the response still says that
error: Cross origin http://localhost forbidden
How to change the "Origin" of the HTTP request I create with Axios under jsdom to other than http://localhost? I need it to be http://localhost:3000 so that the API server allows me.

It turns out jsdom is the one who makes the origin localhost, and prevented cross-origin requests. From https://github.com/axios/axios/issues/1180 I was able to solve my problem. In the test suite, place this code before any HTTP requests by axios:
axios.defaults.adapter = require('axios/lib/adapters/http')
This will make Axios use NodeJS's HTTP adapter instead of JSDOM's XMLHttpRequests. This way there will be no Cross-origin problem.

Supplimenting #Arkross answer,
axios/lib/adapters/http is not exposed by the package.
Instead, you can do this:
axios.defaults.adapter = 'http'
// or
axios.request({
adapter: 'http',
...
})

Related

HTTP request working from Postman and Node but not React

There are a few questions similar to this on Stack Overflow, and none of the proposed solutions worked, so I'll walk through the case and what I've tried.
I have a server application hosted on Cloud Run, which can only be accessed with the appropriate Bearer token in the request Authorization header. I've tried accessing it via Postman and an Axios request from a local Nodejs server, with the Authorization header, and it worked fine. With React (create-react-app specifically), I get the following error: Access to XMLHttpRequest at 'https://myserver-lhp5a9xp5a-ue.a.run.app/api/rules' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
On the server side, I get the 403 error that Cloud Run gives when the incorrect Authorization token is passed. Also, when I allow unauthenticated access from the Cloud Run side (so remove the need for an Authorization header), the request works fine, so it looks like this is indeed an issue with the Authorization header and not CORS.
In addition, I'm handling CORS on the server side. Here's my server-side code:
var express = require('express');
var router = express.Router();
const cors = require('cors');
router.options('/api/rules', cors());
router.get('/api/rules', cors(), (req, res, next) => {
res.status(200).send()
});
Here's my React code:
const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_API_BASE_URL
});
const buttonClickHandler = async (event) => {
const resp = await axiosInstance.get('/api/rules'
, {
headers: {
'Authorization': 'Bearer eyJhbGciOiJSUzI1NiIsImtpZ...' // I used this token within the same minute when trying the request via Postman or from my Nodejs app, so a token expiry isn't the issue.
}
}
)
console.log(resp.data)
}
Here's what I tried so far:
Using fetch instead of axios - same error
Using the same token, within the same 5 seconds, to send the request from Postman or a Nodejs server - it worked fine.
Using an axios interceptor to set the Authorization - same error
Removing the single quotes around Authorization - same error
Sending the request to my Nodejs server instead and doing a console.log of the header to make sure the Authorization token is being passed correctly (it is)
Not using an an axios instance but spelling out the full URL in the request - same error
Trying a different endpoint on my Cloud Run server - same error
Deploying my React app to be served from a https endpoint and sending the request from there - same error
Adding Accept: '*/*' to the headers
Adding 'Accept': '*/*' to the headers
Adding 'Content-Type': 'application/json' to the headers
All combinations of the three above points
I found the answer after some digging, thanks #aniket-kolekar for pointing me in the right direction.
When Postman or a Nodejs server query an endpoint like GET, POST, PUT, DELETE, they send the call without checking the OPTIONS first. Create-React-App does.
The service I was querying is hosted on Cloud Run and doesn't allow unauthenticated invocations. So while I was including the authorization header to make my GET call, it wasn't being included in the pre-flight OPTIONS call. In fact, CORS prevents auth headers from being included in an OPTIONS call.
A Cloud Run PM replied in this post that this is a known issue with Cloud Run. The way I'll get around it for now is to host two services on Cloud Run - one that doesn't require authentication, and effectively acts as a proxy server to route calls from the client service to the shielded server service.
TLDR;
CORS is a mechanism built into the web browser. It’s not a UI code issue.
To fix CORS problems, you need to make changes on the API (server) side.
Here is the behind the scenes working:
Browser: Sends OPTIONS call to check the server type and getting the headers before sending any new request to the API endpoint. Where it checks for Access-Control-Allow-Origin. Taking this into account Access-Control-Allow-Origin header just specifies which all CROSS ORIGINS are allowed, although by default browser will only allow the same origin.
Postman: Sends direct GET, POST, PUT, DELETE etc. request without checking what type of server is and getting the header Access-Control-Allow-Origin by using OPTIONS call to the server.
You will have to configure Access-Control-Allow-Origin header in your server to resolve the CORS issue.

Socket IO allows CORS request from curl, but not from client application

I am making an application in which i have a node backend, and an angular frontend.
I am using socket.IO to communicate between my client and server.
I was facing CORS issue, which i solved (tried to) as:
const io: SocketIOServer = new SocketIOServer(server, {
cors: {
origin: true
}
}); // only for development
But still, upon making my request from my client app (running in another port) gives me CORS issue.
So i went to my terminal, and made a request with curl,as shown here:
me#Desktop:~$ curl "http://127.0.0.1:5000/socket.io/?EIO=4&transport=polling&t=NV5sBAn"
0{"sid":"jAhPIEEkdy8EY8I_AAAD","upgrades":["websocket"],"pingInterval":25000,"pingTimeout":5000}
This suggests me that curl is being able to access the server, and is not facing CORS issue.
In my client, i am trying to connect to my server as:
socket = io.connect('http://locahost:5000'); // server running at port 5000
Help me with my issue, so i can connect with my server.
Also on a sidenote: If the version of server.io in the app is 3, the curl request to the server is also failing. only upon server.io version 4, the curl request is passing.
The error in firefox if that helps:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://locahost:5000/socket.io/?EIO=4&transport=polling&t=NV5vi1_. (Reason: CORS request did not succeed).
EDIT: my issue is not being able to connect to the server with client. I showed the curl because it was suggested in the socket.io cors configuration webpage.
Because socket.io initiates its connection with plain http requests, it is subject to CORs restrictions. CURL does not enforce CORs restrictions (like the browser does) so that's why you don't see it there.
You have a couple options:
You can enable this specific CORs request in your server to permit it.
You can specify the {transports: ['websocket']} option for your socket.io connection in the socket.io client code that initiates the connection. This will tell socket.io to immediately start with a webSocket connection which is not subject to CORs.
Curl does not implement CORS security restrictions, thus it will always be able to connect. You have different ports for your frontend and backend which are considered different CORS origins. So you either need to set your allowed origins correctly on your server and make sure it handles the pre-flight requests, or have the process serving your frontend proxy requests to the backend so that everything is on the same url from the browser’s point of view
Apprrently, the issue was:
socket = io.connect('http://localhost:5000');
changing this to:
socket = io.connect('http://127.0.0.1:5000');
worked!! I have no idea why this name was not getting resolved!!

CORS-enabled server not denying requests

I am trying to use express Cors with my resitfy server and it doesn't seem to be denying requests coming from other ips. I am working locally so I tried setting origin to a random public ip but all of my requests are still going through
Here is my route:
module.exports = function(app) {
var user = require('./controllers/userController');
var cors = require('cors');
var corsOptions = require('./cors.json');
app.post('/auth/signup', cors(corsOptions),user.createUser);
app.post('/auth/login', cors(corsOptions), user.validateUser);
app.post('/auth/generateKeys', cors(corsOptions), user.generateKeys);
app.post('/auth/generateToken', user.generateToken);
};
and here is my cors.json file where I have set a random ip:
{
"origin": "http://172.16.12.123",
"optionsSuccessStatus": 200,
}
With cors set on the route I can see the following in postman but the request is still going through? I would expect an access denied response.
Access-Control-Allow-Origin →http://172.16.12.123
CORS configuration on its own isn’t going to cause a server to deny requests. You can’t cause server-side blocking of requests just through CORS configuration.
The only thing servers do differently when you configure CORS support is just to send the Access-Control-Allow-Origin response header and other CORS response headers. That’s it.
Actual enforcement of cross-origin restrictions is done only by browsers, not by servers.
So no matter what server-side CORS configuration you make to a server, the server still goes on accepting requests from all clients and origins it would otherwise; in other words, all clients from all origins still keep on getting responses from the server just as they would otherwise.
But browsers will only expose responses from cross-origin requests to frontend JavaScript code running at a particular origin if the server the request was sent to opts-in to permitting the request by responding with an Access-Control-Allow-Origin header that allows that origin.
That’s the only thing you can do using CORS config. You can’t make a server only accept and respond to requests from particular origins just by doing any server-side CORS configuration. To do that, you need to use something other than just CORS configuration.
CORS does not prevent anyone from sending GET or POST requests to your application or exposed API URL.
Instead, it indicates to the web browser that AJAX requests are allowed to this server, from the domain they are executed.
But only AJAX requests executed from a domain are CORS-controlled. Entering the URL in the web browser will not activate CORS: it is not a firewall.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
The order of event is:
Domain A executes AJAX on User's browser to request API URL on Domain B
User's browser sends a basic primary request to target Domain B and checks if CORS are allowed for Domain A
If allowed, AJAX request is executed otherwise null is returned

Specific Endpoint: Access Denied (no Access-Control-Allow-Origin header) on API Request in React/Redux App with Node

I cannot access a custom heroku API endpoint in a react/redux app. The heroku endpoint loads up fine in a browser, and I can see the network request coming back on my console, so it seems something in my app itself is preventing it from returning.
It's the classic 'No 'Access-Control-Allow-Origin' header is present on the requested resource' error, but what's perplexing is that I can use another endpoint (such as openweather's API) and don't get this error.
Do I need to use Express or other middleware to deal with Heroku specifically? I'm creating a store with ReduxPromise middleware right now and am using Axios to make the request. Or is there a server side configuration on Heroku that might change this?
Thanks for your help, pretty new to react/redux/node =)
This is a CORS issue, you need to add a cors configuration into your API request and accept the origins on the server side using access-control-allow-origin header and accepting the referrer url of the request.
These are my configuration options when using redux-api to make the requests, look at the documentation for your specific framework:
export const baseOptions = {
mode: 'cors',
credentials: 'include',
headers
}

Spoof Origin header in Node.js

I have a node.js server running express. I'm trying to write several tests for it in order to confirm standard behavior in the future when someone else may work on it. The server processes the request's header, the referrer and origin fields specifically. Using Jquery's ajax method, I was able to send ajax requests to the server. I found that I was able to set the referrer field. However, I can't seem to set the origin. Is there any way to spoof the origin in node.js?
Edit:
For clarification, I'm not running this code through a browser, but from the command line. Normally the ajax call would be run from a webpage, but as I'm writing tests to be run from Mocha, the origin isn't set.
Due to security reasons, the browser will not allow you to manually set your request origins. To spoof your request origin, you will have to make the request server-side:
var http = require('http');
var opt = {
host: 'yoursite.com',
path: '/test',
headers: { origin: 'http://spoofedorigin.com'}
};
http.get( opt );

Resources