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.
Related
I have a simple cross domain service designed to handle the Simple CORS request. I am able to call it through plain xmlHTTP call or jQuery($.ajax) but its throwing Access-Control-Allow-Origin error with AngularJS $http
var url = 'http://some-cross-domain-url/some-path';
$http.get(url); //preflight OPTION verb issued by browser and
//since server is not expecting it, it failed
$.ajax(url, {type: 'GET'}); //working fine as no preflight request sent
CORS request called via Angular $http was triggering preflight (OPTIONS verb) but with plain Ajax call or jQuery Ajax its sent as non-preflighted CORS request as confirmed by debugger network tab in chrome.
As the service designed to handle the Simple CORS request call we need to ensure that Angular also prepare request in a way so that browser issue simple CORS request (See Simple vs Not so simple CORS request at MDN).
Solution: Remove the headers added by Angular by referring Access-Control-Request-Headers
GET request without any headers is treated as simple request
If you have configured Angular $http defaults, it will add these headers into request which makes it not so simple CORS as shown in below image.
All custom HTTP headers sent as Access-Control-Request-Headers when preflighted. Once server allows the communication as per CORS rule, browser sends the actual request(with original Method and Headers etc)
//remove custom headers by looking at Access-Control-Request-Headers
var headers = {
'Authorization': undefined,//undefined tells angular to not to add this header
'pragma': undefined,
'cache-control': undefined,
'if-modified-since': undefined
};
$http.get(url, {
headers: headers
});
I am struggling with fetch over CORS, with authorization:
const token = 'this.is.secret!';
fetch('http://corsserver/api/hello', {
method: 'get',
credentials: 'include',
mode: 'cors',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
}).then(response => {
console.log(response);
}).catch(error => {
console.log(error);
});
When I run this request, Chrome sets the header as:
Request Method:OPTIONS
I look this up and it's a preflighted request.
I mean, that's really cool and stuff. However I can't figure out how to send the actual request after the preflight comes back okay! What's the next step? How do I send the GET request?
I'm missing something very basic here.
Browsers will automatically send the actual GET request if the OPTIONS request is successful. But if the OPTIONS request isn’t successful, browsers will never make the GET request.
And there’s no way make that GET request without the browser doing the OPTIONS request.
So if the browser isn’t doing the GET, it can only mean the OPTIONS must be failing, and you need to find out why. The browser should be logging a message with the reason to its devtools console, so you should start by checking there (and then either edit/update the question to add that info, or post a new separate more-specific question with the error message).
One guess for what the problem could be: Maybe the server is requiring authentication for the OPTIONS request. If so, you need to fix it so that the server doesn’t—because when the browser makes the OPTIONS request, it doesn’t send the Authorization header+value from your code.
Instead in fact the whole purpose of the OPTIONS request in this case is for the browser to ask, Are you OK with getting cross-origin requests that have an Authorization request header?, and for the server to respond in way that indicates if it allows the Authorization header.
So because of that, the server must be configured to respond to any OPTIONS requests (from allowed origins at least) with a 2xx success response, without requiring authentication.
The way you’d know whether the server is requiring authentication for that OPTIONS request is if the CORS message your browser is logging shows a 401 status for the OPTIONS response.
Example code for handling the OPTIONS in a Node.js server environment:
if (req.method === 'OPTIONS') {
res.send();
return;
}
…to make the server send a 200 response with no response body, which is what you want for this. Do note that this explicitly allows all OPTION requests.
I have a REST api made in Laravel 5.1 hosted in a remote server. Now, I', trying to consume that API from another website (that I have in local).
In Laravel I set the required lines to send the CORS headers. I also tested the API using Postman and everything seems to be ok!
In the Frontend
Then, in the website I sent the POST request using ajax, with this code:
var url="http://xxx.xxx.xxx.xxx/apiLocation";
var data=$("#my-form").serialize();
$.ajax({
type: "POST",
url: url,
data: data,
headers: { 'token': 'someAPItoken that I need to send'},
success: function(data) {
console.log(data);
},
dataType: "json",
});
Buy then I get this error in the console:
XMLHttpRequest cannot load http://xxx.xxx.xxx.xxx/apiLocation.
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost' is therefore not allowed access.
In the Backend
In the API I set this (using a Laravel Middleware to set the headers):
return $next($request)
->header('Access-Control-Allow-Origin', '*')
->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
So, I'm confused about where is exactly the problem.
In the server? but then why with Postman work fine?
Is in the Ajax call? so, then what should I add?
Your backend code must include some explicit handling for OPTIONS requests that sends a 200 response with just the configured headers; for example:
if ($request->getMethod() == "OPTIONS") {
return Response::make('OK', 200, $headers);
}
The server-side code also must send an Access-Control-Allow-Headers response header that includes the name of the token request header your frontend code is sending:
-> header('Access-Control-Allow-Headers', 'token')
but then why with Postman work fine?
Postman isn’t a web app and isn’t bound by same-origin restrictions placed on web apps by browsers to block them from making cross-origin requests. Postman is a browser bolt-on for convenience of testing requests in the same way they could be made outside the browser using curl or whatever from the command line. Postman can freely make cross-origin requests.
https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS in contrast explains how browsers block web apps from making cross-origin requests but also how you can un-block browsers from doing that by configuring your backend to send the right CORS headers.
https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Preflighted_requests explains why the browser is sending that OPTIONS request your backend needs to handle.
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 am getting an absurd error while using angular $http with post method.
While sending $http POST requests, it's not attaching Content-type header and due to which i get an error "Response for preflight has invalid HTTP status code 404".
This is my code-
var obj={
"mobile":"hello",
"password":"asjd"
}
$http({
url: 'http://cbsatwork.com/laundry/api-authentication',
method: 'POST',
data:obj,
headers: {
"Content-Type": "application/json"
}
})
Although if i remove the "data:obj" line from the code, it works and i get usual response from the server.
I have looked into many answers, but could not get anything working for me.
EDIT:
I tried using $.ajax() method from jQuery, and it worked totally fine. no issues. so i do not think that there is any issue with my server.
Response for preflight has invalid HTTP status code 404
This error indicates that most likely there's a CORS problem. A browser sends two requests. The first request is OPTIONS and the other is POST, which you configure in your example. The problem is unlikely to be with the Content-Type header, because this header is sent using POST, not OPTIONS. And it's the OPTIONS request that fails. Check if your server supports OPTIONS request and sends back correct CORS required headers