Update: Two things: One, I was issuing the POST requests for jQuery and Angular from two different domains. I created a jsfiddle to test both requests from the same domain, and they both work. I tried to make the fiddle pretty general so that you can swap out the config values and test an XML POST request with basic http authentication for any site that you need to. The other thing is that, using the Angular $http service, the Data-Type header results in the following error: Request header field Data-Type is not allowed by Access-Control-Allow-Headers in preflight response. So I removed that header. I'm still troubleshooting why the request is working from one domain and not the other. Once I figure that out, I'll update and mark as resolved. /update
The following jQuery POST request with basic authentication works as expected:
var mapstory = {
xml: '<?xml version="1.0" encoding="UTF-8"?> <wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" service="WFS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd"> <wfs:Update xmlns:feature="http://www.geonode.org/" typeName="geonode:dja_remote_service_edit_test"><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:FeatureId fid="garbage_id" /></ogc:Filter></wfs:Update></wfs:Transaction>',
url: 'https://demo.mapstory.org/geoserver/wfs/WfsDispatcher',
auth: authToken //base64 encoded string of 'user:password'
};
$.ajax({
url: mapstory.url,
beforeSend: function(xhr) {
xhr.setRequestHeader("Authorization", "Basic " + mapstory.auth);
},
type: 'POST',
contentType: 'text/html',
dataType: 'xml',
processData: false,
data: mapstory.xml,
success: function(data) {
console.log(data);
}
});
Here are the response headers:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin,Content-Type,Accept, Authorization, x-requested-with
Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE
Allow: GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS
Connection: keep-alive
Content-Length: 0
Date: Fri, 01 Jul 2016 21:06:50 GMT
Server: nginx
access-control-allow-origin: http://localhost:8888
However, the following in AngularJS (v1.2.21) doesn't work:
var mapstory = {
xml: '<?xml version="1.0" encoding="UTF-8"?> <wfs:Transaction xmlns:wfs="http://www.opengis.net/wfs" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" service="WFS" version="1.0.0" xsi:schemaLocation="http://www.opengis.net/wfs http://schemas.opengis.net/wfs/1.0.0/wfs.xsd"> <wfs:Update xmlns:feature="http://www.geonode.org/" typeName="geonode:dja_remote_service_edit_test"><ogc:Filter xmlns:ogc="http://www.opengis.net/ogc"><ogc:FeatureId fid="garbage_id" /></ogc:Filter></wfs:Update></wfs:Transaction>',
url: 'https://demo.mapstory.org/geoserver/wfs/WfsDispatcher',
auth: authToken //base64 encoded string of 'user:password'
};
var config = {
withCredentials: true
};
config.headers = {
'Authorization': 'Basic ' + mapstory.auth,
'Content-Type': 'text/html',
'Data-Type': 'xml'
};
$http.post(mapstory.url, mapstory.xml, config)
.success(function(data) {
console.log(data);
});
I receive the following response headers from this request:
Content-Language: en
Content-Type: text/xml;charset=UTF-8
Date: Fri, 01 Jul 2016 20:52:13 GMT
Server: WSGIServer/0.1 Python/2.7.11
Vary: Accept-Language, Cookie
X-Frame-Options: SAMEORIGIN
I get a valid XML response from the Angular request above, but the response indicates that the request wasn't authorized.
I tried swapping out the Content-Type value with application/xml, but this didn't have any effect on the result.
I've read through the Angular $http documentation and approached a number of other resources, such as this Stack Overflow question which describes a similar issue. A recommended solution is to remove the X-Requested-With default header. This step is apparently not necessary in Angular versions above 1.2 (here's a closed issue for that on Angular's Github), but I tried it anyhow. No luck.
Any guidance on this would be wonderful. Thank you very much for taking the time to read and consider this!
It looks like the server does not recognize angular as a same domain application. So what you must do is to include in the web server headers the value: access-control-allow-origin: * to allow requests from "outside" domains.
Related
I actually do not understand this issue. I am not very much into SSL and certificates.
A script on test.kanubox.de (You can try it there and look at the source code) uses ajax to call rest server on sandbox.api.kehrwasser.com/kanubox/v1. Obviously CORS is needed and works well without SSL, thus I assume that CORS is set up correctly. The header data on an OPTIONS-request (preflight) to the API confirms
Access-Control-Allow-Origin: *
Upgrade: h2,h2c
Pragma: no-cache
Keep-Alive: timeout=5, max=100
Content-Type: application/json
Content-Encoding: gzip
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
Server: Apache/2.4
Expires: Fri, 19 Aug 2016 12:15:58 GMT
Access-Control-Max-Age: 500
Access-Control-Allow-Headers: x-requested-with, Content-Type, origin, authorization, accept, client-security-token, Access-Control-Allow-Origin, X-Frame-Options
But when I switch to https://test.kanubox.de and call the API at https://sandbox.api.kehrwasser.com/kanubox/v1 I get CORS error from FireFox like "(Cross-Origin blocked)
Reason: CORS-Header 'Access-Control-Allow-Origin' missing
(Translated error message)
The certificate is from my hoster and verified by my hoster itself. I'm not sure but is it "self-signed" then? So maybe FF blocks it because it doesn't trust it?
Here is my code:
var test = angular.module("test", []);
test.constant('apiConfig', {
apiUrl: "https://sandbox.api.kehrwasser.com/kanubox/v1"
});
test.controller("TestController", function($scope, $http, apiConfig) {
var credentials = { mail: "user#mailserver.com", password: "12345" };
// POST REQUEST VIA SSL
$http({
url: apiConfig.apiUrl + "/users/auth/",
method: 'POST',
data: credentials
}).success(function(data, status, headers, config) {
$scope.variable = data;
}).error(function(data, status, headers, config) {
$scope.variable = data;
});
});
If I browse to https://test.kanubox.de/, then the server certificate is not known in my firefox browser. It is indead a self signed certificated, issued by "Hostpoint DV SSL CA - G2" itself!
To make that SSL certificate work, you need the "Hostpoint" root certificate in your browser. That is exactly how you made it work! So, is was a trusted ROOT certificate issue.
When the SSL problem is solved, then you can look at the CORS issue.
The certificate which is used in https://sandbox.api.kehrwasser.com/kanubox/v1/ is a issued by the well known CA "Let's incrypt". That works fine.
I am making the following JQuery cross domain ajax post request from a phonegap (appgyver steroids) app.
function do_something(callback_method, some_vars)
{
var stringified_some_vars = JSON.stringify(some_vars);
$.ajax({
type: "POST",
url:"http://www.somedomain.com/endpoint",
data: {'some_vars' : stringified_some_vars},
async: true,
dataType: 'json',
crossDomain: true,
xhrFields: {
withCredentials: true
},
success: function(result)
{
var myObject = $.parseJSON(result);
callback_method(myObject);
},
error: function(fail)
{
supersonic.logger.debug(fail);
}
});
}
The post request is successfully sent to the server (Google Appengine - Python) - i.e. the server fires the relevant method. However, when the server response is received the jQuery Ajax method doesn't fire the success handler - it instead fires the error handler. The error text prints to console as
{"readyState":0,"responseText":"","status":0,"statusText":"error"}
The headers in the json response from the server are as follows:
Content-Length: 0
Cache-Control: no-cache
Access-Control-Allow-Origin: *
Content-Type: application/json
the content of the response is as expected. and is written using
text_to_send = json.dumps(python_dict)
self.response.headers.add_header('Access-Control-Allow-Origin', '*')
self.response.headers['Content-Type'] = 'application/json'
self.response.write(text_to_send)
It's not clear to me where this error is coming from. Allowing cross domain requests doesn't seem to have fixed the issue. jsonp GET requests work fine - but obviously these aren't allowed for POST requests. Could anyone tell me where I'm going wrong?
Edit 1
Following the suggestion of #Faisal I adjusted the server code as follows
from urlparse import urlparse
uri = urlparse(self.request.referer)
origin = '{}://{}'.format(uri.scheme, uri.netloc)
self.response.headers.add_header('Access-Control-Allow-Origin', origin)
self.response.headers['Content-Type'] = 'application/json'
The headers are now
Content-Length: 0
Cache-Control: no-cache
Access-Control-Allow-Origin: http://localhost
Content-Type: application/json
However the same error is encountered
I think if it's withCredentials: true you need your origin to be exact match instead of wildcard (*). Here is a quick code to get it from referer. But you should probably also check if its one of the allowed origins:
from urlparse import urlparse
uri = urlparse(self.request.referer)
origin = '{}://{}'.format(uri.scheme, uri.netloc)
Edit
Try adding:
self.response.headers.add_header('Access-Control-Allow-Credentials', 'true')
First off, I just wanted to say that I have read through all of the other threads relating to this topic, but haven't had any luck. Here's a breakdown of the issue:
Goals
Retrieve a CSRF token from Sails when the Ember Application starts
Inject that CSRF token into every AJAX request that is initiated from the Ember Application
To satisfy goal 1, I created an Ember Initializer that runs when the application first boots up (if there is a better place for this, I'm totally open to suggestions). To satisfy goal 2, I retrieve the CSRF token from Sails and then attempt to use Ember.$.ajaxSetup() to ensure the CSRF token is passed either as a header (X-CSRF-Token) or parameter (_csrf). I also ensure that I'm using the withCredentials option to ensure the cookie is set. Here's the code:
// initializers/csrf.js
import Ember from 'ember';
import config from '../config/environment';
export function initialize() {
Ember.$.get(config.APP.API_URL + '/csrfToken').then(function(result) {
Ember.$.ajaxSetup({
data: {
'_csrf': result._csrf
},
xhrFields: { withCredentials: true }
});
}, function(error) {
console.log(error);
});
}
export default {
name: 'csrf',
initialize: initialize
};
All of this appears to work as I can see in Chrome dev tools that the CSRF token is being retrieved and when I make an AJAX request, I see the data appended to the POST data or added as a header (tried both options). Here's the code I'm running and all of the associated headers:
Ember.$.post(config.APP.API_URL + '/auth/register', {
'email': _this.get('email'),
'password': _this.get('password')
}).then(function(response) {
console.log('It worked!');
});
Request Headers
POST /auth/register HTTP/1.1
Host: localhost:1337
Connection: keep-alive
Content-Length: 82
Accept: */*
Origin: http://localhost:4200
CSP: active
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
DNT: 1
Referer: http://localhost:4200/signup
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: sails.sid=s%3AbrABhErTY3-ytTWOKFJ2KBj7DCAzaLDc.apD60Sd%2BW85GSbTfJ7E3B2PrUwnhOsW6GlNpZTu9jFg
Form Data
_csrf:yP7GDiU2-YGmLBfBvQtMPT3-hRpnfK0x-AfA
email:test#test.com
password:q1w2e3r4
Response Headers
HTTP/1.1 403 Forbidden
Vary: X-HTTP-Method-Override
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Credentials: true
Content-Type: text/html; charset=utf-8
Content-Length: 13
Date: Thu, 09 Jul 2015 08:11:34 GMT
Connection: keep-alive
As you can see from the Response headers, I end up receiving a 403 Forbidden - CSRF Mismatch from Sails. Now, here's where it gets a little weird: first, I'm able to run this just fine with Postman. I retrieve a token and then post that token along with the data to the /auth/register url and it works as expected.
I'm also tried removing the initializer and running the following code:
Ember.$.ajaxSetup({
xhrFields: { withCredentials: true }
});
Ember.$.get(config.APP.API_URL + '/csrfToken').then(function(result) {
Ember.$.post(config.APP.API_URL + '/auth/register', {
'email': _this.get('email'),
'password': _this.get('password'),
'_csrf': result._csrf
}).then(function(response) {
console.log('It worked!');
});
});
This works. However, at this point, I'm at somewhat of a loss as to what the issue actually is. Appreciate any help I can get.
Thanks in advance!
James
#jdixon04, Can you try URL-encoding the CSRF token before sending it through POST? The token mismatch will occur if the token is getting altered from the original.
I found this issue in Github: https://github.com/balderdashy/sails/issues/2266.
I hope this will solve your issue. Do try it and let me know if it works. Thanks.
#jdixon04, got here from your post on my github issue. Actually, isn't the CSRF token going to change at each request made to the server? Then you approach to fix the token when the frontend load cannot cope with this, you may have to fetch the token before each request and use ajaxPrefilter to pass it to the request.
Is that actually related to ember-data-sails? It seems to me you're doing pure ajax here! If you look in my config, you'll realise that pure ajax calls (for authentication as well) are exempted from csrf as I could not make it work as I wished :\ .
Add the x-csrf-token header like this:
Ember.$.ajaxSetup({
headers: {
'X-CSRF-TOKEN': result._csrf
},
xhrFields: { withCredentials: true }
});
Okay so I can access the HTTP ajax response header using
xhr.getAllResponseHeaders();
but it doesn't seem to get the Date with it, though its there:
[Chrome]
**Response Header**
Access-Control-Allow-Origin:*
Cache-Control:no-cache
Content-Length:8092
Content-Type:application/json; charset=utf-8
**Date:Thu, 15 Jan 2015 16:30:13 GMT**
Expires:-1
Pragma:no-cache
Server:Microsoft-IIS/8.0
TotalCount:116
X-AspNet-Version:4.0.30319
X-Powered-By:ASP.NET
and the code only shows this :
[output on alert xhr.getAllResponseHeaders();]
Cache-Control: no-cache
Pragma: no-cache
Content-Type: application/json; charset=utf-8
Expires: -1
here's the the ajax call:
$.ajax({
url: url,
type: "GET",
contentType: "application/json;charset=utf-8",
async: true,
success: function (data,status, xhr) {
displayNewData(data);
alert(xhr.getAllResponseHeaders());
},
error: function () {
alert(url);
}
});
Is there a way where I can get the Date in the response header?
It might be the case you are making a CORS request and the headers are filtered out for security reasons.
See also similar question about missing response headers in ajax request.
The solution might be to set this HTTP header in the server response:
Access-Control-Expose-Headers: Date
This Helped :
var req = new XMLHttpRequest();
req.open('GET', document.location, false);
req.send(null);
var headers = req.getAllResponseHeaders().toLowerCase();
alert(headers);
Accessing the web page's HTTP Headers in JavaScript
in your success method:
success: function (data,status, xhr) {
console.log(xhr.getResponseHeader('Date'));
},
If response is a success
res=xhr.getResponseHeader('Date');
if response fails
res=data.getResponseHeader('Date');
If you are using Nginx, you can put below code in Nginx config file:
add_header 'Access-Control-Expose-Headers' 'Date';
for real config example:
location / {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Expose-Headers' 'Date';
root /usr/local/nginx/html;
index index.html index.htm;
}
After restarting your nginx service, you can call getAllResponseHeaders again and it will show you the "Date".
Let's say, I have a Tornado web server (localhost) and a web page (othermachine.com), and the latter contains javascript that needs to make cross-domain ajax calls to the Tornado server.
So I set up my Tornado as such:
class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "http://www.othermachine.com")
self.set_header("Access-Control-Allow-Credentials", "true")
self.set_header("Access-Control-Allow-Methods", "GET,PUT,POST,DELETE,OPTIONS")
self.set_header("Access-Control-Allow-Headers",
"Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, X-Requested-By, If-Modified-Since, X-File-Name, Cache-Control")
And my javascript makes a jQuery call:
$.ajax({
type: 'GET',
url: "http://localhost:8899/load/space",
data: { src: "dH8b" },
success: function(resp){
console.log("ajax response: "+resp);
},
dataType: 'json',
beforeSend: function ( xhr ) {
xhr.setRequestHeader('Content-Type', 'text/plain');
xhr.setRequestHeader('Access-Control-Request-Method', 'GET');
xhr.setRequestHeader('Access-Control-Request-Headers', 'X-Requested-With');
xhr.withCredentials = true;
}
});
But I get the lovely XMLHttpRequest cannot load http://localhost:8899/load/space?src=dH8b. Origin http://www.othermachine.com is not allowed by Access-Control-Allow-Origin error. I can't tell which side of jQuery / Tornado (or both?) am I not setting up correctly.
According to dev tools, these are the headers the jQuery request is sending:
Request Headers
Accept:*/*
Origin:http://www.othermachine.com
Referer:http://www.othermachine.com/athletes.html?src=BCYQ&msgid=6xjb
User-Agent:Mozilla/5.0 ...
If I simply make a request from my browser's url field I get a '200 OK' with this:
Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type, User-Agent, X-Requested-With, X-Requested-By, Cache-Control
Access-Control-Allow-Methods:GET,POST
Access-Control-Allow-Origin:http://www.othermachine.com
Content-Length:0
Content-Type:text/html; charset=UTF-8
Server:TornadoServer/2.2.1
Does that mean Tornado is doing its job? I tried to follow the advice of all the stackoverflow CORS+jQuery posts (e.g. this), to no avail. CORS in concept seems simple enough, but maybe I am fundamentally misunderstanding what is supposed to happen in a CORS transaction... please help! Thanks in advance.
Nevermind, coding too late and too long causes one to trip over things the size of typos. For the record, this is all you need for jQuery:
var data = { msgid: "dH8b" },
url = "http://localhost:8899/load" + '?' + $.param(data);
$.getJSON( url, function(resp){
console.log("ajax response: "+resp+" json="+JSON.stringify(resp));
});
And this is all you need for Tornado:
class BaseHandler(tornado.web.RequestHandler):
def set_default_headers(self):
self.set_header("Access-Control-Allow-Origin", "http://www.othermachine.com")
Using jQuery 1.7.2, Tornado 2.2.1.
try setting origin to be: othermachine.com. it should be a domain name, not a website address