I'm making a query to a web service using jQuery AJAX. My query looks like this:
var serviceEndpoint = 'http://example.com/object/details?version=1.1';
$.ajax({
type: 'GET',
url: serviceEndpoint,
dataType: 'jsonp',
contentType: 'jsonp',
headers: { 'api-key':'myKey' },
success: onSuccess,
error: onFailure
});
When I execute this, I get a status error of 403. I do not understand why my call results in having the status code 403. I'm in control of the security on my service and it is marked as wide-open. I know the key is valid, because I'm using it in another call, which works. Here is the call that works:
var endpoint = 'http://example.com/object/data/item?version=1.1';
$.ajax({
type: 'POST',
url: endpoint,
cache: 'false',
contentType:'application/json',
headers: {
'api-key':'myKey',
'Content-Type':'application/json'
},
data: JSON.stringify({
id: 5,
count:true
}),
success: onDataSuccess,
error: onDataFailure
});
I know these are two different endpoints. But I'm 100% convinced this is not a server-side authentication or permission error. Once again, everything is wide open on the server-side. Which implies that I'm making some mistake on my client-side request.
I feel I should communicate that this request is being made during development. So, I'm running this from http://localhost:3000. For that reason, I immediately assumed it was a CORS issue. But everything looks correct. The fact that my POST request works, but my GET doesn't has me absolutely frustrated. Am I missing something? What could it be?
3 Answers 3
The reason of 403 error is you are not sending headers. Since you are making a CORS request, you cannot send any custom headers unless server enables these header by adding Access-Control-Allow-Headers to the response.
In a preflighted-request, client makes 2 requests to the server. First one is preflight (with OPTIONS method) and the second one is the real request. The server sends Access-Control-Allow-Headers header as a response of the preflight request. So it enables some headers to be sent. By this way your POST request can work because the POST request is a preflight-request. But for a GET request, there is no preflight to gather Access-Control-Allow-Headers header and browser doesn't send your custom headers in this case.
A workaround for this issue:
As a workaround, set your dataType and contentType to json as the following:
var serviceEndpoint = 'http://example.com/object/details?version=1.1';
$.ajax({
type: 'GET',
url: serviceEndpoint,
dataType: 'json',
contentType: 'json',
headers: { 'api-key':'myKey' },
success: onSuccess,
error: onFailure
});
By this way, your get request will be a preflighted request. If your server enables the api-key with Access-Control-Allow-Headers header, it will work.
Sample server configuration for the above request (written in express.js):
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', '*');
res.setHeader('Access-Control-Allow-Headers', 'api-key,content-type');
res.setHeader('Access-Control-Allow-Credentials', true);
ADDED:
Actually, contentType should be either application/javascript or application/json while doing a jsonp request. There is no contentType as jsonp.
2 Comments
If you look at the API page for jQuery's Ajax call, it mentions the following in the Content-Type section:
Note: For cross-domain requests, setting the content type to anything other than application/x-www-form-urlencoded, multipart/form-data, or text/plain will trigger the browser to send a preflight OPTIONS request to the server.
That page doesn't really mention what a "preflight OPTIONS request" is, but I found some interesting links when looking that phrase up online:
- Preflight Blob Request
- HTML 5 Rocks Using CORS
- W3C's Cross-Origin Resource Sharing spec
- Using CORS for Cross-Domain Ajax Requests
What's intersting is the code example & the CORS image at the HTML5Rocks page. The image shows how the Ajax calls are being made from the JavaScript code to the browser to the server & how the responses are round-tripping between all 3 of those.
We tend to think of JavaScript + Browser = Client, but in the illustration the author is explaining the difference between the web developer's code & the browser developer's code, where the former is written in JavaScript code, but the latter was written using C, C++ or C# code.
A good packet analyzer tool is Fiddler, which would be similar to Wireshark. Either one of those tools, should show you the pre-flight requests which are being sent from the browser to the server. Most likely, that's where your Ajax request is being blocked at by the server with a 403 Forbidden error.
Comments
See also this post that discusses the problem of the Google Translate extension appending extraneous HTML to certain page elements.
For example, in the above-linked case, Google Translate appended these lines
<p> </p>
<div id="gtx-trans" style="position: absolute; left: -28px; top: -8px;"> </div>
to the TinyMCE editor contents. When the user-entered data was sent to the back-end, it included the Google Translate additions, which precipitated the 403 error.
Comments
Explore related questions
See similar questions with these tags.
/data/part of the url to match the one that works?jsonprequest, it is a script request. Are you sure you wantjsonpand notjson? Also whyJSON.stringify()for headers? GET has no requestcontentType. since there is no body content being sent. You have numerous issues any one of which can be problem/data/part I need to include. I litterally just need to pass in theversionandapi-key. I assumed that I should put theapi-keyas a header. Do I need to set thedataandcontentTypeproperties tojsonp? This seems like it should be a simple call. But clearly, I'm botching it and overlooking something. What should the correct call look like?