Transmit a cookie to sites on the same domain in ajax requests - ajax

My site a.domain exposes a page whose scripts perform AJAX queries (using fetch) to b.domain.
During the first visit to a.domain, a cookie is sent to the client with the following parameters :
"httpOnly" : "true"
"secure" : "true"
"domain" : ".domain"
"sameSite" : tried Strict, Lax and None
b.domain has the following response headers :
"Access-Control-Allow-Origin" : "a.domain"
"Access-Control-Allow-Credentials : true
"Access-Control-Allow-Methods" : "GET, PUT, POST, DELETE, OPTIONS".
"Access-Control-Allow-Headers" : "Content-Type, Authorization, Content-Length, X-Requested-With, Credentials"
And the headers of the fetch requests are :
"Content-Type" : "application/json"
"Accept" : "application/json"
"Credentials" : "include"
The cookie is well transmitted to a.domain or if I visit b.domain directly, but not in AJAX requests.
EDIT : the problem is the same whatever the value of the samesite parameter.
Is there a way to do that ? Thanks

Ok, found it.
The problem was related to a misunderstanding of the credentials parameter in the fetch requests. credentials is not a header but a parameter of the request.
Once the parameter is out of the headers (and written in lowercase) everything works normally.

Related

Laravel API not working without CSRF token (and Headers globally messing around with me)

I am trying to setup an API in my Laravel project, which I would like to consume with Javascript in my own project. The API is stateless, thus my understanding was that CRSF tokens become irrelevant when requesting the API. I am encountering the two following issues:
when I try sending requests without the X-CSRF-TOKEN header, I get 401 Unauthorized
there seem to be a problem with the headers not being properly applied to the response
The requests are made from subdomain client.site.test to subdomain api.site.test . I have made sure CORS is authorized by applying an "InternalCORS" middleware to my API routes.
Middleware - applied to all API routes
class InternalCors {
public function handle($request, Closure $next) {
header('Access-Control-Allow-Origin: https://client.site.test');
header('Access-Control-Allow-Headers: Content-Type, X-Auth-Token, X-CSRF-Token, X-Requested-With, Authorization, Origin');
header('Access-Control-Allow-Methods: GET, POST, PUT');
header('Access-Control-Allow-Credentials: true');
return $next($request);
}
}
API routes
Route::middleware('auth:api')->get('/testApi', function (Request $request) {
return response()->json($request->user()->fullName(), 200);
})->name('testApi');
Ajax query
window.$.ajax({
headers: {
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
xhrFields: {
withCredentials: true
},
type: 'GET',
url: 'https://api.site.test/testApi',
success: function (response) {
console.log(response);
},
error: function (xhr) {
console.error(xhr.responseText);
}
});
Now, even with the middleware explicitely authorizing "Credentials", I was getting a response saying that the header is set not '' instead of 'true'. So I added on top an Nginx directive:
add_header Access-Control-Allow-Credentials "true";
With settings above, the response to the request is "401 Unauthorized". I have doubled checked that the VerifyCsrfToken middleware is only included by default in web routes, not api.
I would like to able to say that adding the CRSF token to the ajax query solves the issue, but it's not that simple.. There are 3 headers that seem to affect the response I get :
[CSRF]: whether I send a X-CSRF-TOKEN header in the Ajax request
[MIDD HEADER]: whether I include header('Access-Control-Allow-Credentials: true'); in the middleware for API routes
[NGINX HEADER]: whether I include add_header Access-Control-Allow-Credentials "true"; in my server configuration for API routes
Here is a recap of the response to Ajax query, depending on the headers set:
[CSRF] [MIDD HEADER] [NGINX HEADER] : value of the 'Access-Control-Allow-Credentials' header in the response is ''
[CSRF] [MIDD HEADER] [NGINX HEADER] : value of the 'Access-Control-Allow-Credentials' header in the response is 'true, true'
[CSRF] [MIDD HEADER] [NGINX HEADER] : 200 - John Smith
[CSRF] [MIDD HEADER] [NGINX HEADER] : value of the 'Access-Control-Allow-Credentials' header in the response is ''
[CSRF] [MIDD HEADER] [NGINX HEADER] : 401 Unauthorized
[CSRF] [MIDD HEADER] [NGINX HEADER] : value of the 'Access-Control-Allow-Credentials' header in the response is ''
I'm at a loss here...
You can Disable CSRF on few routes by editing.
App\Http\Middleware\VerifyCsrfToken
and add your own routes name in protected
$except = [] array.
You can check more here

Angular2 : X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers

I am struggling with this issue today as I am implementing a cross-site API call. The worst thing is it works well from my local environment but once on heroku, it fails with the following error:
XMLHttpRequest cannot load https://restcountries.eu/rest/v1/all. Request header field X-XSRF-TOKEN is not allowed by Access-Control-Allow-Headers in preflight response.
Here is the function triggering the call:
let observable = this._http
.get(GEO_API_URL + query)
.map(response => response.json())
.do(val => {
this.cache = val;
observable = null;
})
.share();
return observable;
Any idea ?
Thanks.
Had the same issue.
In my case the reason was that in my Chrome cookies was saved X-XSRF-TOKEN field. And somehow Chrome added header 'Access-Control-Request-Headers: x-xsrf-token' to OPTION request. In Firefox the same page works fine, in incognito mode Chrome - too.
So I've just delete this cookies field (X-XSRF-TOKEN) and that's all.
In my case I had to add the 'x-xsrf-token' value to 'Access-Control-Allow-Headers' header:
header('Access-Control-Allow-Headers: Content-Type, x-xsrf-token')
see AngularJS: POST Data to External REST API
I cleared cookies, this solved problem.
this helped me in java (expose the headers and then include in the allow headers). This will then show in your HttpResponse object:
response.addHeader("Access-Control-Expose-Headers", "header1");
response.addHeader("Access-Control-Expose-Headers", "header2");
response.addHeader("Access-Control-Expose-Headers", "header3");
response.addHeader("Access-Control-Allow-Headers", "Origin, header1, header2, header3, X-Requested-With, Content-Type, Accept");
The reason is that x-xsrf-token keyword is not in response header Access-Control-Allow-Headers.
I solved this problem in java using following solution:
rsp.setHeader("Access-Control-Allow-Methods", "GET,HEAD,POST,OPTIONS,PUT,DELETE,TRACE,CONNECT");
rsp.setHeader("Access-Control-Allow-Headers", "cache-control,content-type,hash-referer,x-requested-with, x-xsrf-token");
if ("OPTIONS".equals(req.getMethod())) {
rsp.setStatus(HttpServletResponse.SC_OK);
return;
}

OPTIONS request returns "No 'Access-Control-Allow-Origin' header" error during ajax POST to a different domain

I'm struggling with CORS issue. I make a request from js to a different domain, the method allows cross domain request and all works fine with GET but not with POST request. Looks like OPTIONS method is called before the POST and return standard error
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:3000' is therefore not allowed access.
return Response.ok().entity(c).header("Access-Control-Allow-Origin", "*")
.header("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS")
.header("Access-Control-Allow-Headers", "Content-Type, x-xsrf-token, X-Requested-With, Accept, Expires, Last-Modified, Cache-Control").build();
On the client side I use angularjs
$http.post(url, data).success(...)
But also tried with
$.ajax({type:'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}...})
the same result. what else can I do to fix POST request?
Add the below code to your Angular JS application config file
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];

Access-Control-Allow-Origin in preflight response doesn't enable cross-domain access

I am trying to send a CORS request using AJAX to a nodeJS server. I want to return some JSON data. I've found numerous tutorials online that all say the same thing, which I've tried, but I can't get this to work. Here's the AJAX request:
$.ajax({
url: "http://some.other.url.com:8880",
type: "GET",
crossDomain: true,
contentType: 'application/json'
}).then(function(response) {
$scope.allData = jQuery.parseJSON( response );
console.log($scope.allData);
}).fail(function(response) {
});
And here is the code on the server:
var path = url.parse(req.url).pathname,
match = router.match(path),
rescode;
console.log("---: " + req.method);
if (req.method === 'OPTIONS') {
var headers = {};
headers["Access-Control-Allow-Origin"] = "*";
headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
headers["Access-Control-Allow-Credentials"] = false;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept";
res.writeHead(200, headers);
return res.end();
}
I'v also tried it without the return on res.end() i.e. not returning the OPTIONS preflight request, and that doesn't work either.
--Edit--
Here is the actual error message in the console:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://other.domain.com:8880/. This can be fixed by moving the resource to the same domain or enabling CORS.
The server is getting the requests. Both the OPTIONS and then GET requests are hitting the server and being responded to. In fact, in the console log for the page making the AJAX request, I can click on the CORS error and see the response, and it is the correct data. But I can't seem to get the javascript to continue.
In regards to .done vs .then, they seem to work interchangeable. Or at least, in this example, the .then and .fail are working just fine.
You're correctly setting CORS headers in your OPTIONS preflight response, but you also need to set Access-Control-Allow-Origin (either to your origin or *) on your actual GET response. The GET response should respond with the same CORS headers, regardless of whether there was a preflight response or not. This means that it must send the appropriate CORS headers, but it does not need to send anything except for Access-Control-Allow-Origin. (If other non-simple components like non-simple verbs or headers are involved, they will be allowed or denied in the preflight; the actual GET response does not need to worry about them.)
The Enable CORS site has a CORS testing tool to help you see the headers involved in a request that you specify. I've used that tool to set up a test similar to your case (GET with non-simple Content-Type header). If we examine the results of that test (careful -- the steps are presented little bit out of order, but they're all there), we see a preflight response:
Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS
...
Access-Control-Allow-Origin: http://client.cors-api.appspot.com
Access-Control-Allow-Headers: X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept
And the final CORS response:
Content-Length: 0
Content-Type: application/json
Access-Control-Allow-Origin: http://client.cors-api.appspot.com
Cache-Control: no-cache
As you can see, the GET response also has a Access-Control-Allow-Origin header and no other CORS headers. If you have any further uncertainties, feel free to tweak the settings on that tool to run a wide range of other test cases.

jQuery $.ajax(), $.post sending "OPTIONS" as REQUEST_METHOD in Firefox

Having trouble with what I thought was a relatively simple jQuery plugin...
The plugin should fetch data from a php script via ajax to add options to a <select>. The ajax request is pretty generic:
$.ajax({
url: o.url,
type: 'post',
contentType: "application/x-www-form-urlencoded",
data: '{"method":"getStates", "program":"EXPLORE"}',
success: function (data, status) {
console.log("Success!!");
console.log(data);
console.log(status);
},
error: function (xhr, desc, err) {
console.log(xhr);
console.log("Desc: " + desc + "\nErr:" + err);
}
});
This seems to work fine in Safari. In Firefox 3.5, the REQUEST_TYPE on the server is always 'OPTIONS', and the $_POST data does not appear. Apache logs the request as type 'OPTIONS':
::1 - - [08/Jul/2009:11:43:27 -0500] "OPTIONS sitecodes.php HTTP/1.1" 200 46
Why would this ajax call work in Safari, but not Firefox, and how do I fix it for Firefox?
Response Headers
Date: Wed, 08 Jul 2009 21:22:17 GMT
Server:Apache/2.0.59 (Unix) PHP/5.2.6 DAV/2
X-Powered-By: PHP/5.2.6
Content-Length 46
Keep-Alive timeout=15, max=100
Connection Keep-Alive
Content-Type text/html
Request Headers
Host orderform:8888
User-Agent Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1) Gecko/20090624 Firefox/3.5
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language en-us,en;q=0.5
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
Connection keep-alive
Origin http://ux.inetu.act.org
Access-Control-Request-Method POST
Access-Control-Request-Headers x-requested-with
Here is a picture of the Firebug output:
The reason for the error is the same origin policy. It only allows you to do XMLHTTPRequests to your own domain. See if you can use a JSONP callback instead:
$.getJSON( 'http://<url>/api.php?callback=?', function ( data ) { alert ( data ); } );
I used the following code on Django side to interpret the OPTIONS request and to set the required Access-Control headers. After this my cross domain requests from Firefox started working. As said before, the browser first sends the OPTIONS request and then immediately after that the POST/GET
def send_data(request):
if request.method == "OPTIONS":
response = HttpResponse()
response['Access-Control-Allow-Origin'] = '*'
response['Access-Control-Allow-Methods'] = 'POST, GET, OPTIONS'
response['Access-Control-Max-Age'] = 1000
# note that '*' is not valid for Access-Control-Allow-Headers
response['Access-Control-Allow-Headers'] = 'origin, x-csrftoken, content-type, accept'
return response
if request.method == "POST":
# ...
Edit: it seems to be that at least in some cases you also need to add the same Access-Control headers to the actual response. This can be a little bit confusing, since the request seems to succeed, but Firefox does not pass the contents of the response to the Javascript.
This mozilla developer center article describes various cross-domain request scenarios. The article seems to indicate that a POST request with content type of 'application/x-www-form-urlencoded' should be sent as a 'simple request' (with no 'preflight' OPTIONS request). I found , however, that Firefox sent the OPTIONS request, even though my POST was sent with that content type.
I was able to make this work by creating an options request handler on the server, that set the 'Access-Control-Allow-Origin' response header to '*'. You can be more restrictive by setting it to something specific, like 'http://someurl.com'. Also, I have read that, supposedly, you can specify a comma-separated list of multiple origins, but I couldn't get this to work.
Once Firefox receives the response to the OPTIONS request with an acceptable 'Access-Control-Allow-Origin' value, it sends the POST request.
I've fixed this issue using an entirely-Apache based solution. In my vhost / htaccess I put the following block:
# enable cross domain access control
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
# force apache to return 200 without executing my scripts
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule .* / [R=200,L]
You may not need the latter part, depending on what happens when Apache executes your target script. Credit goes to the friendly ServerFault folk for the latter part.
This PHP at the top of the responding script seems to work. (With Firefox 3.6.11. I have not yet done a lot of testing.)
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: POST, GET, OPTIONS');
header('Access-Control-Max-Age: 1000');
if(array_key_exists('HTTP_ACCESS_CONTROL_REQUEST_HEADERS', $_SERVER)) {
header('Access-Control-Allow-Headers: '
. $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
} else {
header('Access-Control-Allow-Headers: *');
}
if("OPTIONS" == $_SERVER['REQUEST_METHOD']) {
exit(0);
}
I had same problem with sending requests to google maps, and solution is quite simple with jQuery 1.5 - for dataType use dataType: "jsonp"
Culprit is preflight request using OPTIONS method
For HTTP request methods that can cause side-effects on user data (in particular, for HTTP methods other than GET, or for POST usage with certain MIME types), the specification mandates that browsers "preflight" the request, soliciting supported methods from the server with an HTTP OPTIONS request method, and then, upon "approval" from the server, sending the actual request with the actual HTTP request method.
Web specification refer to: https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
I resolved the problem by adding following lines in Nginx conf.
location / {
if ($request_method = OPTIONS ) {
add_header Access-Control-Allow-Origin "*";
add_header Access-Control-Allow-Methods "POST, GET, PUT, UPDATE, DELETE, OPTIONS";
add_header Access-Control-Allow-Headers "Authorization";
add_header Access-Control-Allow-Credentials "true";
add_header Content-Length 0;
add_header Content-Type text/plain;
return 200;
}
location ~ ^/(xxxx)$ {
if ($request_method = OPTIONS) {
rewrite ^(.*)$ / last;
}
}
I was looking through source 1.3.2, when using JSONP, the request is made by building a SCRIPT element dynamically, which gets past the browsers Same-domain policy. Naturally, you can't make a POST request using a SCRIPT element, the browser would fetch the result using GET.
As you are requesting a JSONP call, the SCRIPT element is not generated, because it only does this when the Type of AJAX call is set to GET.
http://dev.jquery.com/ticket/4690
We had a problem like this with ASP.Net. Our IIS was returning an Internal Server Error when trying to execute a jQuery $.post to get some html content due to PageHandlerFactory was restricted to respond only GET,HEAD,POST,DEBUG Verbs. So you can change that restriction adding the verb "OPTIONS" to the list or selecting "All Verbs"
You can modify that in your IIS Manager, selecting your website, then selecting Handler Mappings, double click in your PageHandlerFactory for *.apx files as you need (We use Integrated application pool with framework 4.0). Click on Request Restrictions, then go to Verbs Tabn and apply your modification.
Now our $.post request is working as expected :)
Check if your form's action URL includes the www part of the domain, while the original page you have opened is viewed without www.
Typically done for Canonical Urls..
I struggled for hours before stumbling upon this article and found the hint of Cross Domain.
I seems that if o.url = 'index.php' and this file exists is ok and returning a success message in the console. It returns an error if I use url:http://www.google.com
If doing a post request why not using directly the $.post method:
$.post("test.php", { func: "getNameAndTime" },
function(data){
alert(data.name); // John
console.log(data.time); // 2pm
}, "json");
It is so much simpler.
I have posted a clear example of how to solve this if control the server code of the domain you are POSTing to. This answer is touched on in this thread, but this more clearly explains it IMO.
How do I send a cross-domain POST request via JavaScript?
Solution to this is:
use dataType: json
add &callback=? to your url
this worked on calling Facebook API and with Firefox. Firebug is using GET instead of OPTIONS with the above conditions (both of them).
Another possibility to circumvent the problem is to use a proxy script. That method is described for example here
Can you try this without
contentType:application/x-www-form-urlencoded
Try adding the option:
dataType: "json"
function test_success(page,name,id,divname,str)
{
var dropdownIndex = document.getElementById(name).selectedIndex;
var dropdownValue = document.getElementById(name)[dropdownIndex].value;
var params='&'+id+'='+dropdownValue+'&'+str;
//makerequest_sp(url, params, divid1);
$.ajax({
url: page,
type: "post",
data: params,
// callback handler that will be called on success
success: function(response, textStatus, jqXHR){
// log a message to the console
document.getElementById(divname).innerHTML = response;
var retname = 'n_district';
var dropdownIndex = document.getElementById(retname).selectedIndex;
var dropdownValue = document.getElementById(retname)[dropdownIndex].value;
if(dropdownValue >0)
{
//alert(dropdownValue);
document.getElementById('inputname').value = dropdownValue;
}
else
{
document.getElementById('inputname').value = "00";
}
return;
url2=page2;
var params2 = parrams2+'&';
makerequest_sp(url2, params2, divid2);
}
});
}
I had a similar problem with trying to use the Facebook API.
The only contentType which didn't send the Preflighted request seemed to be just text/plain... not the rest of the parameters mentioned at mozilla here
Why is this the only browser which does this?
Why doesn't Facebook know and accept the preflight request?
FYI: The aforementioned Moz doc suggests X-Lori headers should trigger a Preflighted request ... it doesn't.
You need to do some work on server side. I see you are using PHP on server side, but solution for .NET web application is here:
Cannot set content-type to 'application/json' in jQuery.ajax
Do the same in PHP script and it will work. Simply: At first request browser is asking server if is allowed to send such data with such type and second request is the proper/allowed.
Try to add the following:
dataType: "json",
ContentType: "application/json",
data: JSON.stringify({"method":"getStates", "program":"EXPLORE"}),
I used a proxy url to solve a similar problem when I want to post data to my apache solr hosted in another server. (This may not be the perfect answer but it solves my problem.)
Follow this URL: Using Mode-Rewrite for proxying, I add this line to my httpd.conf:
RewriteRule ^solr/(.*)$ http://ip:8983/solr$1 [P]
Therefore, I can just post data to /solr instead of posting data to http://ip:8983/solr/*. Then it will be posting data in the same origin.
I already have this code handling well my cors situation in php:
header( 'Access-Control-Allow-Origin: '.CMSConfig::ALLOW_DOMAIN );
header( 'Access-Control-Allow-Headers: '.CMSConfig::ALLOW_DOMAIN );
header( 'Access-Control-Allow-Credentials: true' );
And it was working fine locally and remotely, but not for uploads when remote.
Something happen with apache/php OR my code, I didn't bother to search it, when you request OPTIONS it returns my header with cors rules but with 302 result. Therefore my browser doesn't recognise as an acceptable situation.
What I did, based on #Mark McDonald answer, is just put this code after my header:
if( $_SERVER['REQUEST_METHOD'] === 'OPTIONS' )
{
header("HTTP/1.1 202 Accepted");
exit;
}
Now, when requesting OPTIONS it will just send the header and 202 result.
Please be advised:
JSONP supports only the GET request method.
*Send request by firefox:*
$.ajax({
type: 'POST',//<<===
contentType: 'application/json',
url: url,
dataType: "json"//<<=============
...
});
Above request send by OPTIONS(while ==>type: 'POST')!!!!
$.ajax({
type: 'POST',//<<===
contentType: 'application/json',
url: url,
dataType: "jsonp"//<<==============
...
});
But above request send by GET(while ==>type: 'POST')!!!!
When you are in "cross-domain communication" , pay attention and be careful.

Resources