Cross Domain REST POST Does Preflight Check But No Follow Up - javascript

I'm new to web programming so bear with me. I've created a simple REST API in python flask, and am hosting it with Apache 2.4. I've tested it via cURL and it works. Now I'm trying to access it via a web interface with jQuery.
The REST api is at http://api.localhost and the website that accesses it is at http://localhost.
The code I'm using to try and do a POST looks like this:
$.ajax({
type: 'POST',
url: 'http://api.localhost/auth',
data: '{"username":"user1", "password":"abcxyz"}',
success: function(data) { console.log(data); alert('data: ' + data); },
contentType: "application/json",
dataType: 'json'
});
However, it seems the success function doesn't run. Looking in dev console (f12) I can see that instead of a POST to that URL, and OPTIONS HTTP request is made. My understanding is this is a Cross-Origin Resource Sharing (CORS) preflight check, to make sure it's OK for localhost to access api.localhost.
I've added the following lines to my apache config for api.localhost:
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS"
It seems to be working to because the OPTIONS request returns a 200 (and no other data). However, there is no follow up. My understanding is since the server is saying it's OK for anyone to POST to api.localhost, that it should go ahead and do the POST next, but it doesn't.
Here are the preflight check request headers:
Host: api.localhost
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101
Firefox/52.0
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
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type
Origin: http://localhost
Connection: keep-alive
Cache-Control: max-age=0
Here are the response headers for that same request (remember, status 200):
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Origin: *
Allow: POST, OPTIONS
Connection: Keep-Alive
Content-Encoding: gzip
Content-Length: 20
Content-Type: text/html; charset=utf-8
Date: Thu, 07 Dec 2017 00:17:08 GMT
Keep-Alive: timeout=5, max=100
Server: Apache/2.4.29 (Debian)
Vary: Accept-Encoding
You can see that the server is saying any domain is fine (*) and that POST is OK. However, there is no follow up POST. What am I missing?
Thanks.

http://api.localhost/auth must also send Access-Control-Allow-Headers: content-type.
So to your Apache config, you also need to add this:
Header always set Access-Control-Allow-Headers "content-type"
That’s necessary because the contentType: "application/json" part of your frontend code adds a Content-Type: application/json header to the request, and any values for the Content-Type request header other than text/plain, application/x-www-form-urlencoded, or multipart/form-data will trigger browsers to send a CORS preflight OPTIONS request.
So if you have your http://api.localhost/auth server send back the Access-Control-Allow-Headers: content-type response header, that should cause the preflight to succeed, and so cause the browser to move on to making the POST request from your frontend code.

Related

Custom Request Headers not being sent with a JavaScript Fetch Request

I am trying to use JavaScripts Fetch() API to send an AJAX request to my PHP OAuth server.
My issue is that I need to send a Request header Authorization with
'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM3MWFjZTRiZWE1NmViZDQ5YzQ1OTFkMmJiY2E4NGMwOTM2N2JiMjZmNjZiMmJkOTkxZmE3YmU0NTJjYWZmODBkMmZlYmJhYjFjNzMyMmM1In0.eyJhdWQiOiIxIiwianRpIjoiMzcxYWNlNGJlYTU2ZWJkNDljNDU5MWQyYmJjYTg0YzA5MzY3YmIyNmY2NmIyYmQ5OTFmYTdiZTQ1MmNhZmY4MGQyZmViYmFiMWM3MzIyYzUiLCJpYXQiOjE0ODA1NTE5NzIsIm5iZiI6MTQ4MDU1MTk3MiwiZXhwIjoxNTA3NzY3OTcyLCJzdWIiOiI0Iiwic2NvcGVzIjpbImNydWQtYm9va21hcmstY29sbGVjdGlvbnMiLCJjcnVkLWJvb2ttYXJrLXRhZ3MiLCJjcnVkLWJvb2ttYXJrcyJdfQ.YZWbwDXx4gsUtmvLP1GOY2XUnQ5MC030ymfoV6AYjgQMOqKnsmwrsYrTv5q6MVzo50_SMLipyA9t2VgpZkXj6tOdzA-v9idGnV8JVy-GZeceRlhgl7mpnAe1icI5P62mfhHQiyAdF2cfH6OKsy3ONzyzXRw1_pm-5o_qzcNbUGIATnKr5jXbYElRZZlh7-TUBQ2aSnEsu_fOR2rX5zZ_2dhpAMyE5GOK-UODhjs9PQVLXEEtnlzXyRIdjv-2YTuwJzLryHoooP4N5SncvkBlA6mk0IXVnVnJAMkomnkulofmn1k1niK6Dnzk8OANjbi_uPNbj4W2EtHA0tENNKDfAJ9maiHQZgmpWVk_rkKPrw04BogJNq682mgZhRwYjMM8tD7Rzmrb1DRI8_dM60O5AL5Nm5sxXzKd946OGmMLSQ_OnvAXsAN52KdnlQNW2RzMkFErdrXADMf1g1u7WjH-yo7G9wf-2QMVt8ejrWIbj3_7eBUHIWc4VYg_-IzFMkXl_WXKh12n1RnB62nvyz0IQ5aHbNP0_jIFZixHs0CjuNKikoWguEWfRL78eb2cTyxYMn3E2Yh31RMMaJzF1mM-we05D9WPyQUPKNMuIUQVVRI4GOvny9IJUuGbjcQVLsA-EMbXTBcf9LdRo62gQTNyeCYIshgw3MhX9OazDGK7Xks'
This is the code I have right now....
var app = {
init: function() {
this.apitest();
},
apitest: function() {
var request = new Request('http://bookmarkapi.dev/api/user', {
method: 'GET',
mode: 'no-cors',
headers: new Headers({
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjM3MWFjZTRiZWE1NmViZDQ5YzQ1OTFkMmJiY2E4NGMwOTM2N2JiMjZmNjZiMmJkOTkxZmE3YmU0NTJjYWZmODBkMmZlYmJhYjFjNzMyMmM1In0.eyJhdWQiOiIxIiwianRpIjoiMzcxYWNlNGJlYTU2ZWJkNDljNDU5MWQyYmJjYTg0YzA5MzY3YmIyNmY2NmIyYmQ5OTFmYTdiZTQ1MmNhZmY4MGQyZmViYmFiMWM3MzIyYzUiLCJpYXQiOjE0ODA1NTE5NzIsIm5iZiI6MTQ4MDU1MTk3MiwiZXhwIjoxNTA3NzY3OTcyLCJzdWIiOiI0Iiwic2NvcGVzIjpbImNydWQtYm9va21hcmstY29sbGVjdGlvbnMiLCJjcnVkLWJvb2ttYXJrLXRhZ3MiLCJjcnVkLWJvb2ttYXJrcyJdfQ.YZWbwDXx4gsUtmvLP1GOY2XUnQ5MC030ymfoV6AYjgQMOqKnsmwrsYrTv5q6MVzo50_SMLipyA9t2VgpZkXj6tOdzA-v9idGnV8JVy-GZeceRlhgl7mpnAe1icI5P62mfhHQiyAdF2cfH6OKsy3ONzyzXRw1_pm-5o_qzcNbUGIATnKr5jXbYElRZZlh7-TUBQ2aSnEsu_fOR2rX5zZ_2dhpAMyE5GOK-UODhjs9PQVLXEEtnlzXyRIdjv-2YTuwJzLryHoooP4N5SncvkBlA6mk0IXVnVnJAMkomnkulofmn1k1niK6Dnzk8OANjbi_uPNbj4W2EtHA0tENNKDfAJ9maiHQZgmpWVk_rkKPrw04BogJNq682mgZhRwYjMM8tD7Rzmrb1DRI8_dM60O5AL5Nm5sxXzKd946OGmMLSQ_OnvAXsAN52KdnlQNW2RzMkFErdrXADMf1g1u7WjH-yo7G9wf-2QMVt8ejrWIbj3_7eBUHIWc4VYg_-IzFMkXl_WXKh12n1RnB62nvyz0IQ5aHbNP0_jIFZixHs0CjuNKikoWguEWfRL78eb2cTyxYMn3E2Yh31RMMaJzF1mM-we05D9WPyQUPKNMuIUQVVRI4GOvny9IJUuGbjcQVLsA-EMbXTBcf9LdRo62gQTNyeCYIshgw3MhX9OazDGK7Xks'
})
});
return fetch(request).then(app.checkStatus).then(app.parseJSON);
},
checkStatus: function(response) {
if (response.status >= 200 && response.status < 300) {
return response
} else {
var error = new Error(response.statusText)
error.response = response
throw error
}
},
parseJSON: function(response) {
return response.json()
},
};
The result of this in Google Chrome Dev Tools Network tab under Headers is this:
General:
Request URL:http://bookmarkapi.dev/api/user
Request Method:GET
Status Code:401 Unauthorized
Remote Address:127.0.0.1:80
Response Headers:
HTTP/1.1 401 Unauthorized
Date: Thu, 01 Dec 2016 00:41:43 GMT
Server: Apache/2.4.10 (Win32) OpenSSL/1.0.1i PHP/5.6.23
X-Powered-By: PHP/5.6.23
Cache-Control: no-cache
Content-Length: 28
Keep-Alive: timeout=5, max=99
Connection: Keep-Alive
Content-Type: application/json
Request Headers:
GET /api/user HTTP/1.1
Host: bookmarkapi.dev
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
accept: application/json
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36
Referer: http://localhost/labs/webdevapp/tmp/tools/lab/manage_bookmark_list_tags.html
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
These 2 images below show the same exact data being sent to the URL using the app Postman and everything works great as the correct headers get sent so my server responds correctly.
This image shows my server returning the authenticated resources and the headers:
Access-Control-Allow-Headers →Authorization
Access-Control-Allow-Methods →GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin →*
These headers are not shown when making a request with JS Fetch() in Chrome or Firefox!
Also when I use a plugin to send custom headers in my browsers, it works correctly so the issue seems to be with the Fetch() function not send my custom headers.
Any ideas how I can get JS Fetch() to send my custom headers with my request?
From all the articles I have seen on using Fetch, it seems I have done it correctly however it does not send the headers in Google Chrome or Firefox so apparently it is not working! I have even tried adding a Fetch Pollyfil just to be on the safe side with no luck!
I finally resolved the issue. The browser request was actually the CORS preflight request in which my server was not returning the correct data so that is why fetch never sent my headers.
I now use this Laravel package https://github.com/barryvdh/laravel-cors to add cors support to a PHP laravel app and al is working great!
You are using mode: 'no-cors', this is prevent browser to sending OPTIONS request before GET. With OPTIONS request browser "notifies the server that when the actual request is sent, it will be sent with SOME custom headers. The server now has an opportunity to determine whether it wishes to accept a request under some circumstances.".
Mozilla HTTP OPTIONS method
if you do not implement that circumstances, your request will die at OPTIONS.

$http POST call in angularjs returns HTTP status code 405

I try to make a POST call from a javascript client to a foursquare API called addvenue.
This the API endpoint documentation link.
But the server returns 405 - Method not allowed. Here is the snippet making the call
var postdata = {'oauth_token':$scope.access_token_foursquare,
'v':'20141217','name':'randomlisting',
'll':'44.3,37.2','m':'foursquare'};
var req = {
method: 'POST',
url: 'https://api.foursquare.com/v2/venues/add',
headers: {
'content-type': 'application/x-www-form-urlencoded'
},
data: postdata
}
$http(req).then(function(response){
console.log(response);
});
Following is the Request and response packet for the above call.
Remote Address:103.245.222.185:443
Request URL:https://api.foursquare.com/v2/venues/add
Request Method:OPTIONS
Status Code:405 Method Not Allowed
**Request Headers**
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, authorization, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:api.foursquare.com
Origin:http://localhost:9000
Referer:http://localhost:9000/foursquare
User-Agent:Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36
**Response Headers**
Accept-Ranges:bytes
Access-Control-Allow-Origin:*
Connection:Keep-Alive
Content-Length:90
Content-Type:application/json; charset=utf-8
Date:Wed, 17 Dec 2014 12:15:15 GMT
Keep-Alive:timeout=10, max=50
Server:nginx
Tracer-Time:1
Via:1.1 varnish
X-Cache:MISS
X-Cache-Hits:0
X-Served-By:cache-sn87-SIN
I also studied about CORS issue. In my case the server is allowing all origins, as seen in the response headers. I am struck with this issue and could not proceed further.
Any help would be appreciated.
Thanks in advance.
Request Method:OPTIONS
The client is making a pre-flight OPTIONS request to the server.
An OPTIONS request is automatically made by the browser before making a non-simple (e.g. not a GET) cross domain (CORS) request.
The purpose of the OPTIONS request is a quick check with the server to ensure that the client is permitted to make the POST before actually making the POST. Thus the client makes 1 or 2 requests.
An OPTIONS request and if the OPTIONS request responds with success (not a 405) then make the POST.
The OPTIONS request is failing most likely because you have not stated in your server response that your server supports OPTIONS requests.
Add this header to your server response ..
Access-Control-Allow-Methods: POST, GET, PUT, DELETE, OPTIONS
Then it should all work.
See https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests for more info

Annoying issue with CORS and Header Authentication with Angular

This is my trouble:
With $http I'm trying to make a request. This is the response:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8383' is therefore not allowed access.
I just enable all that I need to make CORS request. This is on my server:
httpResponse.setHeader("Access-Control-Allow-Origin", "*");
httpResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
httpResponse.setHeader("Access-Control-Max-Age", "3600");
httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, bbtoken, CUSTOM_AUTH_TOKEN, Authorization");
httpResponse.setHeader("Access-Control-Expose-Headers", "CUSTOM_AUTH_TOKEN");
This is the code from Angular:
$http({
method: 'GET',
url: 'http://www.myurl\:8080/mypath/myservice',
headers: {
CUSTOM_AUTH_TOKEN: SessionService.currentUser.token
}
})
.success(function(data, status, headers, config) {
successCallback(data);
})
.error(function(data, status, headers, config) {
console.error(data);
});
But I found an infamous 401 Auth Error.
If I try with cURL or RestConsole (chrome), all is ok... I cannot understand the mistake!
Thanks!
UPDATE
This is the source request header
OPTIONS /mypath/blabla HTTP/1.1
Host: myhost
Connection: keep-alive
Cache-Control: no-cache
Pragma: no-cache
Access-Control-Request-Method: GET
Origin: http://localhost:8383
User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.114 Safari/537.36
Access-Control-Request-Headers: custom_auth_token
Accept: */*
Referer: http://localhost:8383/index.html
Accept-Encoding: gzip,deflate,sdch
Accept-Language: it
UPDATE WITH RESPONSE
HTTP/1.1 401 Unauthorized
Server: Apache-Coyote/1.1
WWW-Authenticate: Basic realm="Restricted Service"
Content-Type: text/html;charset=utf-8
Content-Language: en
Content-Length: 1079
Date: Sun, 08 Jun 2014 08:08:32 GMT
You are adding a custom HTTP request header to the request, this prevents the request from being simple and makes it preflighted.
Before the browser will make the GET request, it will make an OPTIONS request to ask if the extra header is acceptable.
You need to set up your server so it will respond with suitable CORS headers to the OPTIONS request as well as the GET request.
Solved: The problem came from by Spring Security. A nonsense configuration about security, needs authentication (by custom filter) on all request. OPTION included. By for HTTP standards, the headers inside the OPTION do not contain values, but only name (of the headers)
This is the solution for my trouble:
<intercept-url pattern="/**" access="permitAll" method="OPTIONS" requires-channel="any"/>

XSS Problems with Ajax GET Request

I have the following coffee script which performs some sort of login:
signIn: (url, completion) ->
$.ajax
method: 'GET'
url: url
dataType: 'json'
error: (jqXHR, status, errorThrown) ->
completion false, errorThrown
success: (data)->
completion true, data.Identifier
When I check the given URL in the browser I get a valid JSON Response back.
However, when this call is executed using JavaScript I get the following error in the console . Please note that I have changed the URLs for obfuscation:
XMLHttpRequest cannot load http://my.servicedomain.com/session/someIdentifier?access_token=secret.
Origin http://html.server.net is not allowed by Access-Control-Allow-Origin.
These are my headers, which I get from the my.servicedomain.com server:
HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 1417
Content-Type: application/json; charset=utf-8
Expires: -1
Server: Microsoft-IIS/8.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization
Server: Microsoft-IIS/8.0
X-AspNet-Version: 4.0.30319
X-Powered-By: ASP.NET
X-Powered-By: ASP.NET
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization
Date: Wed, 10 Jul 2013 14:24:35 GMT
Vary: Accept-Encoding
Content-Encoding: gzip
Connection: Keep-Alive
Why do I get this error, even though I have Access-Control-Allow-Origin: * in the response header?
I have just figured out the answer myself. I knew that I had duplicated headers in my response, but I was assuming this would not be a problem.
It looks like this is a Problem according to the CORS Spec:
If the response includes zero or more than one Access-Control-Allow-Origin header values, return fail and terminate this algorithm.
This is also described in this SO Thread:
Will duplicate "Access-Control-Allow-Origin: *" headers break CORS?

Firefox 3 CORS XMLHTTPRequest doesn't seem to work

I implemented a REST service, which I'm calling from a javascript application on a different domain.
I'm attempting to do a GET request, and setting the Authorization: header, with a custom authentication scheme.
Because I'm setting a custom header, Firefox will start with a preflight OPTIONS request. This request looks like this (simplified):
OPTIONS /v1/articles HTTP/1.1
Host: example.org
User-Agent: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101206 Ubuntu/10.10 (maverick) Firefox/3.6.13 FirePHP/0.5
Origin: http://example.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: authorization
My response is as follows:
HTTP/1.1 200 OK
Date: Wed, 15 Dec 2010 16:36:47 GMT
Server: Apache/2.2.9 (Debian) PHP/5.3.3-0.dotdeb.1 with Suhosin-Patch mod_vhost_hash_alias/1.0
X-Powered-By: PHP/5.3.3-0.dotdeb.1
Access-Control-Allow-Origin: http://example.com
Access-Control-Request-Method: GET,POST,PUT,DELETE,HEAD,OPTIONS
Access-Control-Request-Headers: Authorization, X-Authorization
Content-Length: 2
Content-Type: application/json
After this, the actual GET request is simply not performed. I'm worried I made a mistake in my response, but I can't seem to spot it.
The other problem is that I've found no way to get a detailed error message. As you can see, I also tried X-Authorization instead of Authorization.
My questions:
Is there something wrong with my response? How can I find more details about the problem?
Thanks!
I needed a fresh look at this. The next morning I realized the correct headers are:
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,HEAD,OPTIONS
Access-Control-Allow-Headers: Authorization, X-Authorization

Categories

Resources