Possibly I'm still not completely understanding all the ins and outs of browser security, but i think what I'm trying to achieve is relatively simple.
I have a page served 100% over HTTPS, I'm using basic auth, with the username & password being held by the browser and (should) be sent with every request from there onwards.
I have added the credentials: 'same-origin' to the fetch function call and for GET requests its included. but the same code path with a POST request does not include the header in the request.
fetch("/center/57023368c4d6931600216494", {
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
credentials: "same-origin",
method: "GET"
})
fetch("/users/find", {
headers: {
"Content-Type": "application/json",
Accept: "application/json"
},
credentials: "same-origin",
method: "POST",
body: "{\"center\":\"US testing\"}"
})
I don't think this is a CORS issue since everything is talking to the same domain. Ive tried expanding to credentials: 'include' but no difference. And I've seen the problem in both Safari & Chrome.
There seems to be little or no feedback so I'm really stuck as to weather this is a spec / implementation issue or a browser issue or a "your not using the code correctly" issue, any help appreciated
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.
I have a wcf restful service that I need to get working in Chrome. I've read around and added all sorts of headers, etc but nothing is working. The POST works fine in IE, but when I get to Chrome, I get a 405 error on the OPTIONS request telling me that the method is not allowed. I've read where some people have added stuff to their global.asax file, but really don't feel like that should be necessary. I don't even have a global asax file and creating one just for the sole purpose of getting to work in chrome for development only seems crazy.
I'm using Aurelia's HTTP-Client library which uses just a simple XMLHttpRequest. Here's how it's configured:
this.Client = new HttpClient()
.configure(x => {
x.withBaseUrl("http://localhost/MyServices/MyService.svc/")
});
And I'm making the call like so:
this.Client.post("PostData/" + name,
{
version: 1
})
.then(resp => {
console.log(resp);
})
Here are the headers that I have added in my WCF Restful service WebConfig:
<add name="Access-Control-Allow-Origin" value="*" />
<!-- Not sure if these even do anything -->
<add name="Access-Control-Allow-Headers" value="Content-Type, Accept, X-Requested-With" />
<add name="Access-Control-Allow-Methods" value="OPTIONS, GET, POST" />
Then in my service itself:
[OperationContract]
[WebInvoke(Method = "POST", UriTemplate = "/PostData/{name}", RequestFormat = WebMessageFormat.Json, ResponseFormat = WebMessageFormat.Json, BodyStyle = WebMessageBodyStyle.Wrapped)]
Data myData(byte id, string name);
Like I said above, it works fine in IE, but chrome gives me a preflight error. Here's a snippet of the fiddler response:
HTTP/1.1 405 Method Not Allowed
Entity
Content-Length: 1565
Content-Type: text/html; charset=UTF-8
Security
Access-Control-Allow-Headers: Content-Type, Accept, X-Requested-With
Access-Control-Allow-Methods: OPTIONS, GET, POST
Access-Control-Allow-Origin: *
Allow: POST
I also find that if I add x.withHeader("Content-Type", "application/json; charset=utf-8") to my Http-Client configuration, Chrome doesn't even do a GET request. It throws the error.
To sum it all up, I can do GET requests but not POST requests in Chrome. How do I fix it?
I use a gzip json static file on my server (actually, it's my test server) and the data i receive is always compressed.
Here is my code :
$http({
url :'app/assets/json/makes2v.json.gz',
method: "GET",
headers: { 'Accept-Encoding': 'gzip' }})
.success(function(data, status, headers, config) {
console.log(data);
});
I use angular 1.3.15 and Chrome.
In the console I have this error:
Refused to set unsafe header "Accept-Encoding"
Any help will be appreciated.
Thanks,
You shouldn't set the request header "Accept-Encoding". This is set automatically by the browser. See this Q&A for a list a browsers that accept gzip.
On the server side, you will need to set the Content-Encoding to:
Content-Encoding: gzip
Then the response should be automatically decompressed by the browser.
When I have this code
$.ajax({
type: 'POST',
//contentType: "application/json",
url: 'http://localhost:16329/Hello',
data: { name: 'norm' },
dataType: 'json'
});
in Fiddler I can see following raw request
POST http://localhost:16329/Hello HTTP/1.1
Host: localhost:16329
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
Accept: application/json, text/javascript, */*; q=0.01
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Referer: http://localhost:14693/WebSite1/index.html
Content-Length: 9
Origin: http://localhost:14693
Pragma: no-cache
Cache-Control: no-cache
name=norm
But what I'm trying is to set content-type from application/x-www-form-urlencoded to application/json. But this code
$.ajax({
type: "POST",
contentType: "application/json",
url: 'http://localhost:16329/Hello',
data: { name: 'norm' },
dataType: "json"
});
Generates strange request (which I can see in Fiddler)
OPTIONS http://localhost:16329/Hello HTTP/1.1
Host: localhost:16329
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-ru,ru;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Origin: http://localhost:14693
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Pragma: no-cache
Cache-Control: no-cache
Why is that? What is OPTIONS when it should be POST there? And where is my content-type set to application/json? And request parameters has gone for some reason.
UPDATE 1
On server side I have really simple RESTful service.
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
public class RestfulService : IRestfulService
{
[WebInvoke(
Method = "POST",
UriTemplate = "Hello",
ResponseFormat = WebMessageFormat.Json)]
public string HelloWorld(string name)
{
return "hello, " + name;
}
}
But for some reason I can't call this method with parameters.
UPDATE 2
Sorry for not answering so long.
I've added these headers to my server response
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET, OPTIONS
It didn't help, I have Method not allowed error from server.
Here is what my fiddler says
So, now I can be sure that my server accepts POST, GET, OPTIONS (if response headers work like I expect). But why "Method not allowed"?
In WebView response from server (you can see Raw response on picture above) looks like this
It would seem that removing http:// from the URL option ensures the the correct HTTP POST header is sent.
I don't think you need to fully qualify the name of the host, just use a relative URL as below.
$.ajax({
type: "POST",
contentType: "application/json",
url: '/Hello',
data: { name: 'norm' },
dataType: "json"
});
An example of mine that works:
$.ajax({
type: "POST",
url: siteRoot + "api/SpaceGame/AddPlayer",
async: false,
data: JSON.stringify({ Name: playersShip.name, Credits: playersShip.credits }),
contentType: "application/json",
complete: function (data) {
console.log(data);
wait = false;
}
});
Possibly related:
jQuery $.ajax(), $.post sending "OPTIONS" as REQUEST_METHOD in Firefox
After some more research I found out the OPTIONS header is used to find out if the request from the originating domain is allowed. Using fiddler, I added the following to the response headers from my server.
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET, OPTIONS
Once the browser received this response it then sent off the correct POST request with JSON data. It would seem that the default form-urlencoded content type is considered safe and so does not undergo the extra cross domain checks.
It looks like you will need to add the previously mentioned headers to your servers response to the OPTIONS request. You should of course configure them to allow requests from specific domains rather then all.
I used the following jQuery to test this.
$.ajax({
type: "POST",
url: "http://myDomain.example/path/AddPlayer",
data: JSON.stringify({
Name: "Test",
Credits: 0
}),
//contentType: "application/json",
dataType: 'json',
complete: function(data) {
$("content").html(data);
}
});
References:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
http://enable-cors.org/
https://developer.mozilla.org/en/http_access_control
I can show you how I used it
function GetDenierValue() {
var denierid = $("#productDenierid").val() == '' ? 0 : $("#productDenierid").val();
var param = { 'productDenierid': denierid };
$.ajax({
url: "/Admin/ProductComposition/GetDenierValue",
dataType: "json",
contentType: "application/json;charset=utf-8",
type: "POST",
data: JSON.stringify(param),
success: function (msg) {
if (msg != null) {
return msg.URL;
}
}
});
}
So all you need to do for this to work is add:
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
as a field to your post request and it'll work.
I recognized those screens, I'm using CodeFluentEntities, and I've got solution that worked for me as well.
I'm using that construction:
$.ajax({
url: path,
type: "POST",
contentType: "text/plain",
data: {"some":"some"}
}
as you can see, if I use
contentType: "",
or
contentType: "text/plain", //chrome
Everything works fine.
I'm not 100% sure that it's all that you need, cause I've also changed headers.
If you use this:
contentType: "application/json"
AJAX won't sent GET or POST params to the server.... don't know why.
It took me hours to lear it today.
Just Use:
$.ajax(
{ url : 'http://blabla.example/wsGetReport.php',
data : myFormData, type : 'POST', dataType : 'json',
// contentType: "application/json",
success : function(wsQuery) { }
}
)
I found the solution for this problem here. Don't forget to allow verb OPTIONS on IIS app service handler.
Works fine. Thank you André Pedroso. :-)
I was fighting this same issue and it was caused by a lack of JSON.stringfy() i.e.
data: JSON.stringfy({ name: 'norm' }),
Hope this saves someone else a lot of time!
I had the same issue. I'm running a java rest app on a jboss server. But I think the solution is similar on an ASP .NET webapp.
Firefox makes a pre call to your server / rest url to check which options are allowed. That is the "OPTIONS" request which your server doesn't reply to accordingly. If this OPTIONS call is replied correct a second call is performed which is the actual "POST" request with json content.
This only happens when performing a cross-domain call. In your case calling 'http://localhost:16329/Hello' instead of calling a url path under the same domain '/Hello'
If you intend to make a cross domain call you have to enhance your rest service class with an annotated method the supports a "OPTIONS" http request. This is the according java implementation:
#Path("/rest")
public class RestfulService {
#POST
#Path("/Hello")
#Consumes(MediaType.APPLICATION_JSON)
#Produces(MediaType.TEXT_PLAIN)
public string HelloWorld(string name)
{
return "hello, " + name;
}
//THIS NEEDS TO BE ADDED ADDITIONALLY IF MAKING CROSS-DOMAIN CALLS
#OPTIONS
#Path("/Hello")
#Produces(MediaType.TEXT_PLAIN+ ";charset=utf-8")
public Response checkOptions(){
return Response.status(200)
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Headers", "Content-Type")
.header("Access-Control-Allow-Methods", "POST, OPTIONS") //CAN BE ENHANCED WITH OTHER HTTP CALL METHODS
.build();
}
}
So I guess in .NET you have to add an additional method annotated with
[WebInvoke(
Method = "OPTIONS",
UriTemplate = "Hello",
ResponseFormat = WebMessageFormat.)]
where the following headers are set
.header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Headers", "Content-Type")
.header("Access-Control-Allow-Methods", "POST, OPTIONS")
I got the solution to send the JSON data by POST request through jquery ajax. I used below code
var data = new Object();
data.p_clientId = 4;
data = JSON.stringify(data);
$.ajax({
method: "POST",
url: "http://192.168.1.141:8090/api/Client_Add",
data: data,
headers: {
'Accept': 'application/json',
'Content-Type': 'text/plain'
}
})
.done(function( msg ) {
alert( "Data Saved: " + msg );
});
});
});
I used 'Content-Type': 'text/plain' in header to send the raw json data.
Because if we use Content-Type: 'application/json' the request methods converted to OPTION, but using Content-Type: 'test/plain' the method does not get converted and remain as POST.
Hopefully this will help some one.
Hi These two lines worked for me.
contentType:"application/json; charset=utf-8",
dataType:"json"
$.ajax({
type: "POST",
url: "/v1/candidates",
data: obj,
**contentType:"application/json; charset=utf-8",
dataType:"json",**
success: function (data) {
table.row.add([
data.name, data.title
]).draw(false);
}
Thanks,
Prashant
In the jQuery.ajax documentation, contentType specifies the type of content to send to the server. The default value is "application/x-www-form-urlencoded; charset=UTF-8".
For cross-domain requests, if a content type other than application/x-www-form-urlencoded, multipart/form-data, or text/plain is set, the OPTIONS method will be sent in the request header. It is a check that ajax does by the CORS policies to check if the methods that you request to the server (in this case POST) in your domain, are allowed
If the OPTIONS method on your server where the API is hosted is allowed, the request you made to the server initially is made, in this case the POST is sent.
If you are making the request to the same domain and same port, the OPTIONS method is not sent. If this is the case, one of the solutions is to use "http://localhost:<port>..." or just use relative paths where you are going to make the request.
But if the REST API in your backend is in another domain, so there are two solutions.
Do not put in ajax contentType: "application/json", and send the data either in JSON format as an Object or as a string, or a string with the parameters to send type key=value.
Enable CORS in your Rest API (that is, allow OPTIONS, POST methods, for any domain, or your domain where you make the request).
The problem is that in some cases it is necessary to set the type of content to send as "application/json", because the server where the API is hosted does not accept other types of content.
If you have control over the BackEnd and the REST API, I recommend enabling CORS on it. (Also applies if it is the same domain but different port).
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type
Access-Control-Allow-Methods: POST, GET, OPTIONS
Or also allow other types of content like application/x-www-form-urlencoded or encode it in such a way that it also accepts strings with key=value parameters. This prevents jQuery from sending OPTIONS in its request header.
One last thing: if contentType: "application/json" is used and the server expects "application/json" as well, you should use JSON.stringify() on data, since when sending the request to the server, it seems to take the JSON as a string and not as an object. In my case, not using JSON.stringify() and using contentType: "application/json", returned me a server error with status 500