It appears that jQuery doesn't send along the Authorization header when sending an OPTIONS request before a POST request (or possibly other types). The server I'm trying to reach is returning a 401 status for the OPTIONS request - how can I force jQuery to include the Authorization header, even in this initial request?
$.ajax({
type: "POST",
url: url,
data: postData,
beforeSend: function ajaxBeforeSend(jqXHR) {
jqXHR.withCredentials = true;
jqXHR.setRequestHeader("Authorization", "Basic " + btoa(encodeURIComponent(escape($username.val())) + ":" + encodeURIComponent(escape($password.val()))));
},
success: runReportUrlCallback,
error: runReportErrorCallback
});
I also tried adding username and password to the ajax options, to no avail.
It seems that the 3rd party server has been configured incorrectly without the OPTIONS request in mind.
W3 states that preflight OPTIONS request must:
Exclude user credentials.
User credentials are defined:
The term user credentials for the purposes of this specification means cookies, HTTP authentication, and client-side SSL certificates
See https://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
If the server is in your control then you simply put the OPTIONS request handler in front of your auth check.
If the server is NOT in your control, which seems to be the case here, then you moan at the server administrator explaining they've done it wrong and hope they change it.
Related
AJAX call:
$.ajax({
url: "http://myserver2:296/api/Demo/HelloWorld",
type: "GET",
dataType: 'JSONP',
jsonp: "callback",
headers: { 'API_KEY': 'mykey09090' },
success: function (result) {
console.log(result);
},
error: ajaxFailed
});
function ajaxFailed(xmlRequest) {
alert(xmlRequest.status + ' \n\r ' +
xmlRequest.statusText + '\n\r' +
xmlRequest.responseText);
}
I get the following error: Failed to load resource: the server responded with a status of 403 (Forbidden). However when I use Postman, I just have to add the headers with the http://myserver2:296/api/Demo/HelloWorld url it returns the string.
Can I please get some assistance to resolve the issue.
My goal, is to allow the origin server along with the API key correctly provided to get the data back from the Web Api.
Adding the API_KEY header to the request triggers your browser to first send a CORS preflight OPTIONS request. Any headers you add to a request other than headers defined as CORS-safelisted request-headers will trigger your browser to send a CORS preflight OPTIONS request.
I can’t tell for sure but it seems like the 403 you’re seeing is from your server responding to that OPTIONS request, and saying it doesn’t expect to get OPTIONS requests and doesn’t allow them.
The reason you don’t get this from Postman is that unlike browser engines, Postman does not implement CORS, so it does not send the OPTIONS request. (Postman does not operate under the same-origin Web-security model that browsers enforce for Web applications.)
So to make your client app work as expected for scripted cross-origin access to that server, you must configure the server to respond in the right way to that CORS preflight OPTIONS request.
Code dump:
$.ajax({
type: 'GET',
dataType: 'json',
url: api,
xhrFields: {
withCredentials: true
},
beforeSend: function (xhr) {
xhr.setRequestHeader('Authorization', "Basic [my auth token]");
},
success: function(jd) {
console.log(jd.stringify());
}
});
The problem is that Chrome and Firefox send an OPTIONS preflight when I include a beforeSend, however that OPTIONS request is refused by the API because it doesn't know how to handle an OPTIONS request and treats it like a GET, sees no Authorization header and refuses the request.
The only way I can get this to work is to coerce the browser either to not send an OPTIONS request or include my header with it. I am unable to modify the API that I am using.
I would appreciate it if anyone could advise me.
The reason why browser sends preflight request is that you are using custom headers. Please. read about how to avoid preflight request (content type should be text or html and no custom headers)
If you could not chagne server side the last chance to make it work is to create your custom proxy (for example you can create node server and that node app would take your requests and forward them to those Api Then you will have you own server even in the some domain and this proxy server will send CORS requests to another server domain.
I tried the following code in Postman and it was working. Is there something wrong with the code?
$.ajax({
url: 'http://api.example.com/users/get',
type: 'POST',
headers: {
'name-api-key':'ewf45r4435trge',
},
data: {
'uid':36,
},
success: function(data) {
console.log(data);
}
});
I got this error in my console as below, please advise.
XMLHttpRequest cannot load http://api.example.com/users/get Response
for preflight is invalid (redirect)
I received the same error when I tried to call https web service as http webservice.
e.g when I call url 'http://api.example.com/users/get'
which should be 'https://api.example.com/users/get'
This error is produced because of redirection status 302 when you try to call http instead of https.
This answer goes over the exact same thing (although for angular) -- it is a CORS issue.
One quick fix is to modify each POST request by specifying one of the 'Content-Type' header values which will not trigger a "preflight". These types are:
application/x-www-form-urlencoded
multipart/form-data
text/plain
ANYTHING ELSE triggers a preflight.
For example:
$.ajax({
url: 'http://api.example.com/users/get',
type: 'POST',
headers: {
'name-api-key':'ewf45r4435trge',
'Content-Type':'application/x-www-form-urlencoded'
},
data: {
'uid':36,
},
success: function(data) {
console.log(data);
}
});
The error indicates that the preflight is getting a redirect response. This can happen for a number of reasons. Find out where you are getting redirected to for clues to why it is happening. Check the network tab in Developer Tools.
One reason, as #Peter T mentioned, is that the API likely requires HTTPS connections rather than HTTP and all requests over HTTP get redirected. The Location header returned by the 302 response would say the same url with http changed to https in this case.
Another reason might be that your authentication token is not getting sent, or is not correct. Most servers are set up to redirect all requests that don't include an authentication token to the login page. Again, check your Location header to see if this is where you're getting sent and also take a look to make sure the browser sent your auth token with the request.
Oftentimes, a server will be configured to always redirect requests that don't have auth tokens to the login page - including your preflight/OPTIONS requests. This is a problem. Change the server configuration to permit OPTIONS requests from non-authenticated users.
Please set http content type in header and also make sure the server is authenticating CORS. This is how to do it in PHP:
//NOT A TESTED CODE
header('Content-Type: application/json;charset=UTF-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: DELETE, HEAD, GET, OPTIONS, POST, PUT');
header('Access-Control-Allow-Headers: Content-Type, Content-Range, Content-Disposition, Content-Description');
header('Access-Control-Max-Age: 1728000');
Please refer to:
http://www.w3.org/TR/cors/#cross-origin-request-with-preflight-0
How does Access-Control-Allow-Origin header work?
My problem was that POST requests need trailing slashes '/'.
I had the same problem and it kept me up for days. At the end, I realised that my URL pointing to the app was wrong altogether.
example:
URL: 'http://api.example.com/'
URL: 'https://api.example.com/'.
If it's http or https verify.
Check the redirecting URL and make sure it's the same thing you're passing along.
I had the same error, though the problem was that I had a typo in the url
url: 'http://api.example.com/TYPO'
The API had a redirect to another domain for all URL's that is wrong (404 errors).
So fixing the typo to the correct URL fixed it for me.
My problem was caused by the exact opposite of #ehacinom. My Laravel generated API didn't like the trailing '/' on POST requests. Worked fine on localhost but didn't work when uploaded to server.
I'm developing a javascript app to control some smart TVs but angular pre lights OPTIONS requests before try the POST request (SOAP) I'm trying to send. The devices return a response with a proper Access-Control-Allow-Origin: * but angular refuses to send the POST request.
Of course, I can't change the configurations of the device's server to send another header angular "needs" and I need to send a Cookie and Content-Type.
How can I work around this?
UPDATE with a screenshot of request (bottom) and response (top) headers.
UPDATE with related angular code:
App is configured with:
app.config(['$httpProvider',function($httpProvider) {
$httpProvider.defaults.withCredentials = true;
}])
The request is:
var body = '<?xml version="1.0"?><s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/" s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><s:Body><u:X_SendIRCC xmlns:u="urn:schemas-sony-com:service:IRCC:1"><IRCCCode>{command}</IRCCCode></u:X_SendIRCC></s:Body></s:Envelope>';
var headers = {
"Content-Type": "text/xml; charset=UTF-8",
"SOAPACTION": "urn:schemas-sony-com:service:IRCC:1#X_SendIRCC"
};
return $http({
method:"POST",
url: "http://{ip}/sony/IRCC".replace("{ip}", config.ip),
data: body.replace("{command}", signal),
headers: headers
});
I believe your problem is withCredentials. When you use withCredentials, the server must indicate that allows credentials. In a simple GET request that doesn't require preflighting, the browser is supposed to keep any such response from your app; in a preflighted request, it should not send the actual request.
Here is the best description at mozilla https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials
It says:
but the browser will reject any response that does not have the Access-Control-Allow-Credentials: true header,
and not make the response available to the invoking web content
If you look at the preflight response, you see the headers:
Access-Control-Allow-Headers: "content-type,soapaction"
Access-Control-Allow-Origin: "*"
But the required Access-Control-Allow-Credentials header is not there.
The Options are only fetched if your browser does not know them,
so if you could beforehand load something from the server (regular not via soap) like including an invisible image, your browser should already know the options and not re request them.
So I've got a bookmarklet which executes javascript on other websites, which I want to trigger an 403 Authentication Required header, once the Cache button on it, is clicked. That way, a prompt will come up asking them to login.
The problem is that I'm not meant to provide an authentication header with the ajax request I am making, whilst having Access-Control-Allow-Origin: set to any domain with the * value. I'm supposed to explicitly define which domain I want to allow an 403 Authentication header to appear on, but I can't.
Here's my code.
.htaccess
header set Access-Control-Allow-Origin: *
#header set Access-Control-Allow-Methods: GET, POST, PUT, DELETE
header set Access-Control-Allow-Headers: Authorization
JQuery
$.ajax({
headers : {
"Authorization" : "Basic TVNF3TQtU1BGMjAx6C12bVxzbW4ydHBvaW50OlF3Z5J0eSEyM6Q1"
},
type: "GET",
url: 'http://desbest.uk.to/clickrobot/favicon.png', //image for testing
crossDomain:true,
xhrFields: {
withCredentials: true
},
//contentType: "application/json; charset=utf-8",
//dataType: "json",
success: function(data) {
alert('ok!');
//formatData(format_type,data);
},
error: function(jqXHR, textStatus, errorThrown) {
alert(textStatus + ' / ' + errorThrown);
}
});
The error I get
Cannot use wildcard in Access-Control-Allow-Origin when credentials
flag is true.
I've seen the Diigo bookmarklet do it, so it is possible, but how? Is it possible at all?
Let us take a look at the documentation. There are two things that should be noted:
...the browser will reject any response that does not have the
Access-Control-Allow-Credentials: true header...
and
Important note: when responding to a credentialed request, server must specify a domain, and cannot use wild carding.
The headers that you should return are:
Access-Control-Allow-Origin: [some_origin]
Access-Control-Allow-Credentials: true
You can return the first header by obtaining the Referer using your server script, retrieve the origin from the Referer and then returning the header with your script. In PHP we can do that as follows:
$urllist = parse_url($_SERVER['HTTP_REFERER']);
$origin = $urllist['scheme'] . '://' . $urllist['host'];
header("Access-Control-Allow-Origin: " . $origin);
Update:
You should read Access-Control-Allow-Origin Multiple Origin Domains, in particular this answer. You might not need a PHP file if you can access your httpd.conf
Anyways, your url should not be the image, but the url to your PHP script.
url: 'http://desbest.uk.to/clickrobot/somescript.php'
In your php script you retrieve the origin of the request (which will be the page on which the bookmarklet appeared. And then you can output the header with the origin. Regardless, of where the bookmarklet appears, it should always about the correct header.
I'm not entirely sure, if the authorization dialog will popup using Ajax, even though you specified the correct headers. So, that is why we will have a look at Diigo below.
Diigo uses a different approach, namely: Once 'Sign in' has been clicked, JSONP is uses to request a javascript file which is generated by a server-side script (PHP for example) on their servers. JSONP is an alternative to CORS. JavaScript files from a different can be included in the page's header, with no problems at all, unlike Ajax requests.
If the user is not logged in, the request javascript file sends a 401 header on which the Authentication dialog is shown (this is coded in a server-side script!). The user enters his/her details and based on the entered information return the contents of the javascript file. If the user was successfully logged on it could return something like callback({ signedin : 1}), but otherwise callback({ signedin : 0}).
Now the javascript function callback with certain parameters are called. If the user is signed in, we display the bookmarklet's content.
What to put in your htaccess:
Untested, but you would like to allow all origins and set the 'Access-Control-Allow-Origin' header to the value of the origin. I believe this should do the trick:
SetEnvIf Origin "^(.*)$" ORIGIN_DOMAIN=$1
Header set Access-Control-Allow-Origin "%{ORIGIN_DOMAIN}e" env=ORIGIN_DOMAIN