Cannot make POST request with JSON from react/axios to restify server - json

I have a restify set up like this:
var restify = require('restify');
const server = restify.createServer();
//server.use(restify.plugins.acceptParser(server.acceptable)); // [1]
server.use(restify.plugins.queryParser());
server.use(restify.plugins.bodyParser());
server.use(function(req, res, next) {
console.log(req); // <-- Never see the POST from React here
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
next();
});
I define a bunch GET and POST routes and so far it worked perfectly fine. I called the server from an Android application, Python scripts and for testing simply using curl. No issues at all. Neat!
However, now I've implemented a Web application with React and want to make a request to the restify API using the axios package. GET requests are fine, so I exclude any typos in the URL or such things.
But a POST request like the following won't work:
var data = {"test": "hello"};
axios.post("http://.../api/v1/message/question/public/add", data)
.then(function (response) {
console.log("Test question sent!");
})
.catch(function (error) {
console.log(error);
});
When I check with the browser developer tools, I can see that the browser is trying to make an OPTIONS request (not a POST) to that URL. I assume, from what I've read, that is because the browser is making a "preflighted request". The problem is that I get an 405 Method Not Allowed error:
Request URL: http://.../api/v1/message/question/public/add
Request method: OPTIONS
Remote address: ...
Status code: 405 Method Not Allowed
Response headers (178 B)
Server: restify
Allow: POST
Content-Type: application/json
Content-Length: 62
Date: Sat, 09 Sep 2017 08:16:32 GMT
Connection: keep-alive
Request headers (485 B)
Host: ...
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linu…) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xm…plication/xml;q=0.9,*/*;q=0.8
Accept-Language: en-ZA,en-GB;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://...
DNT: 1
Connection: keep-alive
But why? I allow all Access-Control-Allow-Methods in restify. Everything works, except from POST requests and only when they come from the browser (with the React Web app). I think it's because of the OPTIONS request, but I have no idea how to handle it.
By the way with JSON.stringify(data), the POST requests gets through, but the API expects Json and not a string. And since with all other means it works perfectly fine, I don't want to change the restify code just to accommodate this issue.
[1] If I use this line, I get the following error: AssertionError [ERR_ASSERTION]: acceptable ([string]) is required at Object.acceptParser (/home/bob/node_modules/restify/lib/plugins/accept.js:30:12)

After a couple of more hours, I finally found a solution. I've changed my restify server code as follows:
var restify = require("restify");
var corsMiddleware = require('restify-cors-middleware')
var cors = corsMiddleware({
preflightMaxAge: 5, //Optional
origins: ['*'],
allowHeaders: ['API-Token'],
exposeHeaders: ['API-Token-Expiry']
});
// WITHOUT HTTPS
const server = restify.createServer();
server.pre(cors.preflight);
server.use(cors.actual);
...
(rest is the same as above)
To be honest, I've no real idea what it actual does. But it's working and it the cost me too much energy for today already. I hope it will help others at some point. Everything seems to be rather recent changes from restify.

If your server isn’t allowing OPTIONS by default, you can add an explicit handler:
server.opts(/\.*/, function (req, res, next) {
res.send(200);
next();
});
Another possible problem is that you won’t have the effect intended the following headers:
res.setHeader('Access-Control-Allow-Headers', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
The * wildcard values there are allowed by the spec, but browsers don’t yet support them. So what you must do instead is, explicitly specify in those values the methods and headers to allow:
res.setHeader('Access-Control-Allow-Headers', 'content-type');
res.setHeader('Access-Control-Allow-Methods', 'POST');
That’s because the request headers shown for the CORS preflight OPTIONS request have this:
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://...
And what those request headers indicate is, the browser’s asking the server, Some code running at this origin wants to make a POST request to your server that adds a specific Content-Type to the request. Are you OK with receiving POST requests that add their own Content-Type?
So in response to that preflight OPTIONS request, the browser expects to receive an Access-Control-Allow-Methods response header which explicitly allows POST, along with an Access-Control-Allow-Headers response header which explicitly allows Content-Type.

Related

Calling authentication API from different domain won't set cookies

I am running a Flask API which sets cookies (JWT) if username & password is correct.
I am requesting the API from https://example.ngrok.io and the API is located at https://myAPIDomain.com.
The Set-cookie header is present in the response header, but no cookies are set (viewing Chrome application cookie storage).
Here is the backend configuration:
response.headers['Access-Control-Allow-Origin'] = request.headers['Origin']
response.headers.add('Access-Control-Allow-Credentials', 'true')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type')
response.headers.add('Access-Control-Allow-Headers', 'cache-control')
response.headers.add('Access-Control-Allow-Headers', 'X-Requested-With')
response.headers.add('Access-Control-Allow-Headers', 'Authorization')
response.headers.add('Access-Control-Allow-Headers', 'set-cookie')
response.headers.add('Access-Control-Allow-Headers', 'user-agent')
response.headers.add('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE')
Setting cookie in backend:
resp.headers.add("set-cookie",'cookieKey:cookieValue; Domain=myAPIDomain.com; Max-Age=3600; Secure; Path=/; SameSite=None')
Also tried not specifying the domain:
resp.headers.add("set-cookie",'cookieKey:cookieValue; Domain; Max-Age=3600; Secure; Path=/; SameSite=None')
None of these solutions worked.
Here is a picture of the response headers in Chrome:
https://i.imgur.com/D3cq16Z.jpg
The cookies that the API is supposed to set is used for future API endpoint authentication. So when I send another request:
var myHeaders2 = new Headers();
myHeaders2.append("Content-Type", "application/json");
var requestOptions2 = {
method: 'GET',
headers: myHeaders,
redirect: 'follow',
credentials: 'include'
};
fetch("https://myAPIDomain.com/endpointWhichRequiresCookies", requestOptions2)
.then(response => response.text())
.then(result => console.log(result))
.catch(error => console.log('error', error));
But cookies are not sent (obviously since chrome is not setting the cookies). Here is what the backend receives: https://codebeautify.org/online-json-editor/cb81fb64
I know a workaround would be to reply with cookies to frontend as JSON reply and frontend sends the cookies as different headers (since you cannot send "cookie" header from frontend), but this is not the best solution for us.
The only reason we are calling the API from ngrok is because we are doing localhost testing.
The cookies are being set with Postman, so I do not think the backend is at fault here.
Any ideas? We have been at this for days now, without being able to solve the issue.
Found the solution!
We are sending two requests:
Login Post request with username & password (using Fetch POST)
Request to get information from backend (using Fetch GET) - Backend will use the cookies that were set in request #1 to authenticate the request.
Problem was that we didn't send request #1 with credentials: "include", because we didn't think it was needed for the first request. Our second request always had credentials: "include", but apparently Chrome will disregard the cookies if you do not set credentials: "include" on both requests.

Vue Axios CORS policy: No 'Access-Control-Allow-Origin'

I build an app use vue and codeigniter, but I have a problem when I try to get api, I got this error on console
Access to XMLHttpRequest at 'http://localhost:8888/project/login'
from origin 'http://localhost:8080' has been blocked by CORS policy:
Request header field access-control-allow-origin is not allowed
by Access-Control-Allow-Headers in preflight response.
I have been try like this on front-end (main.js)
axios.defaults.headers.common['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
and this on backend (controller)
header('Access-Control-Allow-Origin: *');
header("Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE");
and vue login method
this.axios.post('http://localhost:8888/project/login', this.data, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PATCH, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "Origin, Content-Type, X-Auth-Token"
}
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err.response);
});
I've searched and tried in stackoverflow but does not work, how I can solve it? thank you so much for your help
CORS is the server telling the client what kind of HTTP requests the client is allowed to make. Anytime you see a Access-Control-Allow-* header, those should be sent by the server, NOT the client. The server is "allowing" the client to send certain headers. It doesn't make sense for the client to give itself permission. So remove these headers from your frontend code.
axios.defaults.headers.common['Access-Control-Allow-Origin'] = '*';
this.axios.post('http://localhost:8888/project/login', this.data, {
headers: {
// remove headers
}
}).then(res => {
console.log(res);
}).catch(err => {
console.log(err.response);
});
For example, imagine your backend set this cors header.
header("Access-Control-Allow-Methods: GET");
That means a client from a different origin is only allowed to send GET requests, so axios.get would work, axios.post would fail, axios.delete would fail, etc.
This may occur you are trying call another host for ex- You Vue app is running on localhost:8080 but your backend API is running on http://localhost:8888
In this situation axios request looking for this localhost:8080/project/login instead of this http://localhost:8888/project/login
To solve this issue you need to create proxy in your vue app
Follow this instruction Create js file vue.config.js or webpack.config.js if you haven't it yet inside root folder
then include below
module.exports = {
devServer: {
proxy: 'https://localhost:8888'
} }
If you need multiple backends use below
module.exports = {
devServer: {
proxy: {
'/V1': {
target: 'http://localhost:8888',
changeOrigin: true,
pathRewrite: {
'^/V1': ''
}
},
'/V2': {
target: 'https://loclhost:4437',
changeOrigin: true,
pathRewrite: {
'^/V2': ''
}
},
}
}
If you select the second one in front of the end point use the V1 or V2
ex - your end point is /project/login before it use V1/project/login or V2/project/login
as per the host
Check this Vue project - https://github.com/ashanoulu/helsinki_city_bike_app/tree/main/Front_End/app-view
Version - Vue3
For more details visit - Vue official documentation
in my case
curl && postman works but not vue axios.post
Access to XMLHttpRequest at 'http://%%%%:9200/lead/_search' from origin 'http://%%%%.local' has been blocked by CORS policy: Request header field access-control-allow-origin is not allowed by Access-Control-Allow-Headers in preflight response.
So, the issue is on vue side not the server!
The server response contains "access-control-allow-origin: *" header
I had the same problem even everything was fine on the server side..
The solution to the problem was that API link I hit was missing the slash (/) at the end so that produced CORS error.
in my case adding this in my php backend API function it worked
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, PUT, POST, DELETE, OPTIONS, post, get');
header("Access-Control-Max-Age", "3600");
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token');
header("Access-Control-Allow-Credentials", "true");
You may try :
At the backend,
npm install cors
then, at the backend app.js , add the following,
const cors = require('cors')
app.use(cors({
origin: ['http://localhost:8082'],
}))
Hopefully, It may help.
Dev Proxy is your solution
With DevProxy you define a specific path, or a wildcard (non static) that Node (the server runs vue-cli dev server) will route traffic to.
Once defined (a single entry in vue.config.js), you call your api with the same URI as your UI (same host and port) and Vue is redirecting the request to the API server while providing the proper CORS headers.
look more at https://cli.vuejs.org/config/#devserver-proxy
I'm building an app in Vue.js and added global headers in the main.js file
Example:
axios.defaults.headers.get['header-name'] = 'value'
For handling CORS issues you may now have to make changes on the client side, it is not just a server issue.
Chrome has a few plugins: https://chrome.google.com/webstore/search/cors?hl=en
for some cases, it is not vue issue. sometimes it's back-end issue.. in my case I've made API in nest JS, and I didn't enable CORS = true.. That's why I am getting CORS policy error.
in my case, the API would return CORS policy, but the problem lied with my url.
my calls were like "https://api.com//call", that extra slash was causing the problem.
changing the url to "https://api.com/call" fixed the error.

Allow Access-Control-Allow-Origin header using HTML5 fetch API

I am using HTML5 fetch API.
var request = new Request('https://davidwalsh.name/demo/arsenal.json');
fetch(request).then(function(response) {
// Convert to JSON
return response.json();
}).then(function(j) {
// Yay, `j` is a JavaScript object
console.log(JSON.stringify(j));
}).catch(function(error) {
console.log('Request failed', error)
});
I am able to use normal json but unable to fetch the data of above api url.
It throws error:
Fetch API cannot load https://davidwalsh.name/demo/arsenal.json. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Like epascarello said, the server that hosts the resource needs to have CORS enabled. What you can do on the client side (and probably what you are thinking of) is set the mode of fetch to CORS (although this is the default setting I believe):
fetch(request, {mode: 'cors'});
However this still requires the server to enable CORS as well, and allow your domain to request the resource.
Check out the CORS documentation, and this awesome Udacity video explaining the Same Origin Policy.
You can also use no-cors mode on the client side, but this will just give you an opaque response (you can't read the body, but the response can still be cached by a service worker or consumed by some API's, like <img>):
fetch(request, {mode: 'no-cors'})
.then(function(response) {
console.log(response);
}).catch(function(error) {
console.log('Request failed', error)
});
This worked for me :
npm install -g local-cors-proxy
API endpoint that we want to request that has CORS issues:
https://www.yourdomain.com/test/list
Start Proxy:
lcp --proxyUrl https://www.yourdomain.com
Proxy Active
Proxy Url: http://www.yourdomain.com:28080
Proxy Partial: proxy
PORT: 8010
Then in your client code, new API endpoint:
http://localhost:8010/proxy/test/list
End result will be a request to https://www.yourdomain.ie/test/list without the CORS issues!
Solution to resolve issue in Local env's
I had my front-end code running in http://localhost:3000 and my API(Backend code) running at http://localhost:5000
Was using fetch API to call the API. Initially, it was throwing "cors" error.
Then added this below code in my Backend API code, allowing origin and header from anywhere.
let allowCrossDomain = function(req, res, next) {
res.header('Access-Control-Allow-Origin', "*");
res.header('Access-Control-Allow-Headers', "*");
next();
}
app.use(allowCrossDomain);
However you must restrict origins in case of other environments like stage, prod.
Strictly NO for higher environments.
I know this is an older post, but I found what worked for me to fix this error was using the IP address of my server instead of using the domain name within my fetch request.
So for example:
#(original) var request = new Request('https://davidwalsh.name/demo/arsenal.json');
#use IP instead
var request = new Request('https://0.0.0.0/demo/arsenal.json');
fetch(request).then(function(response) {
// Convert to JSON
return response.json();
}).then(function(j) {
// Yay, `j` is a JavaScript object
console.log(JSON.stringify(j));
}).catch(function(error) {
console.log('Request failed', error)
});
You need to set cors header on server side where you are requesting data from.
For example if your backend server is in Ruby on rails, use following code before sending back response. Same headers should be set for any backend server.
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Allow-Methods'] = 'POST, PUT, DELETE, GET, OPTIONS'
headers['Access-Control-Request-Method'] = '*'
headers['Access-Control-Allow-Headers'] = 'Origin, X-Requested-With, Content-Type, Accept, Authorization'
If you are use nginx try this
#Control-Allow-Origin access
# Authorization headers aren't passed in CORS preflight (OPTIONS) calls. Always return a 200 for options.
add_header Access-Control-Allow-Credentials "true" always;
add_header Access-Control-Allow-Origin "https://URL-WHERE-ORIGIN-FROM-HERE " always;
add_header Access-Control-Allow-Methods "GET,OPTIONS" always;
add_header Access-Control-Allow-Headers "x-csrf-token,authorization,content-type,accept,origin,x-requested-with,access-control-allow-origin" always;
if ($request_method = OPTIONS ) {
return 200;
}
Look at https://expressjs.com/en/resources/middleware/cors.html
You have to use cors.
Install:
$ npm install cors
const cors = require('cors');
app.use(cors());
You have to put this code in your node server.

200 Response but no JSON Data returned - Restangular issue?

Using restangular and the stub hub api. I can hit this API on the firefox restclient and get a response body back with all the JSON data.
But in my app, I get a 200 but no response body... the content length even says something's there
albeit the api says you just need GET/URI endpoint and Authorization: Bearer {token}
Here's my restangular config
'use strict';
angular.module('myapp')
.config(['RestangularProvider', function(RestangularProvider){
RestangularProvider.setBaseUrl('https://api.stubhub.com/');
RestangularProvider.setDefaultHeaders({
Accept: 'application/json',
Authorization: 'Bearer 198u2190832190831432742(notreallymytoken)'
});
RestangularProvider.setDefaultHttpFields({
withCredentials: false
});
}]);
and here's my controller
$scope.searchEvents = function(){
Restangular.one('search/catalog/events/v2?title="san francisco"').get().then(function(response){
$scope.searchResponse = response;
}, function(response){
console.log("something went wrong");
})
}
How can I begin to debug? I want to keep using restangular so hopefully I can get around this somehow.
I figured out what the problem was and could be in your case too.
The Content-Type for your response would have probably been something like application/javascript and NOT application/json.
Which is why it is not caught by Restangular but by browser or REST clients.

HTTP OPTIONS Pre-flight request Load Cancelled with pending Status in Chrome

I am using Sencha Touch 2.1.0. I am making a HTTP GET call. It is a CORS request. Hence it is sending Pre-flight HTTP OPTIONS command as expected.
I have installed CORS filter on my server and configured it. The calls from my code were going through very well till yesterday. Suddenly today it stopped loading data. When I check the Network calls in Chrome, I see that the OPTIONS method shows up as "Load cancelled"
Method: OPTIONS
Status Text: "Load cancelled"
Type: pending
Initiator: Connection.js:319
I had a similar issue when I configured CORS filter for the first time. When I cleared browser cache, it started working. This time, I am not sure why it suddenly stopped working. It is not getting fixed even when I clear the cache and history in the browser.
If I make the same exact call from HTTPRequestor in Firefox it works very well. Here is the call. I have masked url due to confidentiality reasons.
OPTIONS http://myurl/rest/items?_dc=1358304888220&page=1&start=0&limit=25 HTTP/1.1
Access-Control-Request-Method: GET
Origin: http://localhost:8080
Access-Control-Request-Headers: accept, origin, x-requested-with
The same exact request gives me a very good response from HTTPRequestor. Here is the result:
OPTIONS http://myurl/rest/items?_dc=1358304888220&page=1&start=0&limit=25
Access-Control-Request-Headers: accept, origin, x-requested-with
Access-Control-Request-Method: GET
Origin: http://localhost:8080
-- response --
200 OK
Date: Wed, 16 Jan 2013 03:19:27 GMT
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: http://localhost:8080
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: HEAD, GET, POST, OPTIONS
Access-Control-Allow-Headers: X-Requested-With, Origin, Accept, Content-Type
Content-Length: 0
Strict-Transport-Security: max-age=15768000, includeSubDomains
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Sencha code in the Store to make this call:
proxy: {
type: 'ajax',
method: 'GET',
url: 'http://myurl/rest/items',
withCredentials: true,
useDefaultXhrHeader: false,
disableCaching: false,
headers: {
"Accept": "application/json"
},
failure: function(response) {
if (response.timedout) {
Ext.Msg.alert('Timeout', "The server timed out :(");
} else if (response.aborted) {
Ext.Msg.alert('Aborted', "Looks like you aborted the request");
} else {
Ext.Msg.alert('Bad', "Something went wrong with your request");
}
},
success: function(response){
Ext.Msg.alert(response.responseText);
}
},
autoLoad: true,
Please help me understand how I can fix this issue.
This seems to be an issue due to the last Google Chrome update. When I try with Iron browser, it works.
I was having a similar issue in beta versions of Webkit browsers. As it turns out, the Content-Type header is being set implicitly for XMLHttpRequests containing Blobs, so I had to add that to the Access-Control-Allow-Headers on my upload server.
See: https://bugs.webkit.org/show_bug.cgi?id=99983
I have just solved this problem. Server response Header should be set as:
"Access-Control-Allow-Origin": "*"
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"
You have done this already. I think if GET or POST method contains X-Requested-With header, OPTIONS method will be sent first. X-Requested-With header is in the default XHR header. So we should set 'useDefaultXhrHeader' to false. Ext.data.Store didn't support this parameter. Edit sdk/src/data/Connection.js, change default value of useDefaultXhrHeader to false.