I am making a GET request to my local webservice which I am expecting a 302 response to be returned with a location in the header. However, I get an undefined response back and a network error even though I can see locally that the request is being served and response is being created without any errors in the webservice.
I have tried in Postman and Chrome, and it receives the redirect response and redirects accordingly.
I'm not sure if this is a CORS problem and if so, how can I solve this?
I've already added in the response header for CORS filter
Access-Control-Expose-Headers: Location, [own headers]
Access-Control-Allow-Origin: '*'
Access-Control-Allow-Methods: POST, PUT, GET, OPTIONS, DELETE
Access-Control-Max-Age: [some age]
Access-Control-Allow-Headers: [own headers]
And the location is present in the header when I use Postman
The request I am making using Axios and the config is
const config = {
url: [someURL],
method: 'GET',
headers: {
'customHeader':'token',
},
params: {
[params]
},
maxRedirects: 0,
validateStatus: status => (status >= 200 && status < 300) || status === 302,
};
Any help would really be appreciated as to why the response is undefined when it reaches my JS code, but works fine in Postman and Chrome.
A way I could resolve this is to use HTTP status code 200 and get the location header to redirect, but I want to avoid this because it is technically a redirect response.
The 'customHeader':'token' part of your 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 trigger browsers to send a CORS preflight OPTIONS request.
The reason you don’t get this from Postman is that unlike browser engines, Postman doesn’t implement CORS, so it doesn’t send the OPTIONS request. (Postman does not operate under the same-origin Web-security model that browsers enforce for Web applications.)
If the server doesn’t respond in the right way to CORS preflight OPTIONS requests, your request will fail and the only workaround is to not add that 'customHeader':'token' part to your request, or otherwise construct your request in any way that triggers your browser to do CORS preflight.
Related
I'm trying to call Github REST API from client-side javascript (in browser).
My code does the following (I'm trying to get a zip containing the branch mkdocs_page of a private repository) :
const endpoint = 'https://api.github.com';
const resource = '/repos/astariul/private-gh-pages/zipball/mkdocs_page';
const options = {
mode: 'cors',
headers: {
'Authorization': 'Basic ' + btoa(`${pat}`), // pat contains my Personal Access Token
}
}
return fetch(`${endpoint}${resource}`, options);
But it does not work :
The preflight request fails with 404.
The console error message :
Access to fetch at 'https://api.github.com/repos/astariul/private-gh-pages/zipball/mkdocs_page' from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
In the process of debugging this, I tried to reproduce the problem with curl. And when I specify an authenticated request, it works :
curl --user "<my_PAT_token>" -i https://api.github.com/repos/astariul/private-gh-pages/zipball/mkdocs_page -X OPTIONS
HTTP/1.1 204 No Content
But if the request is not authenticated, it does not work :
curl -i https://api.github.com/repos/astariul/private-gh-pages/zipball/mkdocs_page -X OPTIONS
HTTP/1.1 404 Not Found
Note : it works fine when I'm trying to get the master branch (authenticated or not). However it fails after, when being redirected :
Access to fetch at 'https://codeload.github.com/astariul/private-gh-pages/legacy.zip/refs/heads/main?token=XXX' (redirected from 'https://api.github.com/repos/astariul/private-gh-pages/zipball') from origin 'null' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
Is it a bug in the Github API ? Or I'm doing something wrong ?
There’s no way to force authentication in a preflight request. The preflight is controlled totally by the browser, and nothing about it is exposed in any way that you can manipulate from frontend JavaScript code. And the requirements for the CORS protocol explicitly prohibit browsers from including any credentials in preflight requests.
For a detailed explanation, see the answer at https://stackoverflow.com/a/45406085/.
Since the preflight involves the browser making an OPTIONS request, then in general, if a server requires authentication for OPTIONS request to a particular resource (which seems to be the case for the GitHub URL cited in the question) that’s not at all necessarily an unintended bug.
That’s because the only normal case in which a preflight is performed and an OPTIONS request is sent is for the case of frontend JavaScript code running in a browser. Requests made from server-side code or from code running in shell/command-line environment or from desktop apps or native mobile apps don’t involve sending an OPTIONS request.
So, lack of support for unauthenticated OPTIONS requests to a particular resource would only be a bug if the provider actually intended it to be used from frontend code running in a browser. In other words, it can instead indicate the provider intentionally doesn’t want it to be used from frontend JavaScript code (which seems to be the case for the URL cited in the question).
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.
I've created a script for a google drive which posts to this document.
I invoke it with
var request = $.ajax({
url: "https://script.google.com/macros/s/AKfycbypnRet5l6gUmoGE8oZV2_6da7fImNU12ejHCHCdOambH7UM2CP/exec",
data: serializedData,
type: "POST",
timeout: 10000,
async: true,
crossDomain: true
});
which works from this jsFiddle.
However, when I implement the exact same code into my local project, it doesn't work. I receive
XMLHttpRequest cannot load
https://script.google.com/macros/s/AKfycbypnRet5l6gUmoGE8oZV2_6da7fImNU12ejHCHCdOambH7UM2CP/exec.
No 'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:3000' is therefore not allowed
access. The response had HTTP status code 405.
405 is "Method not allowed", and when I inspect the network traffic I see that for my local request the method is
Request Method:OPTIONS
However, for the one in jsFiddle it is post as expected. I've done some research, and it seems as if options is a preflight request when doing cross origin, and I can't make it use post directly.
The non-working OPTIONS request has has the following headers that aren't in the working Fiddle's request:
access-control-request-headers:accept, content-type
access-control-request-method:POST
If I post the same request to my server (instead of to Google), I see it contains the request header Content-Type:application/json; charset=UTF-8.
How does jsFiddle get through, when the options method is not allowed? Can I somehow make it skip options and go straight to post?
Preflight OPTIONS requests occur when the request is non-simple, either caused by a non-simple header or a non-simple HTTP method.
The access-control-request-headers: accept, content-type header means that you are attempting to send non-simple headers. Accept is always simple, but Content-Type is only simple when it has the value application/x-www-form-urlencoded, multipart/form-data, or text/plain. It must be the case that your code (for whatever reason) is trying to use a non-simple value for Content-Type, and Google is not providing an Access-Control-Allow-Headers response header to allow it.
Instead, you must specify a simple value for Content-Type. You can do this by adding an explicit contentType: "application/x-www-form-urlencoded; charset=UTF-8" property to your $.ajax options object.