CORS issues with jQuery Dropzone and upload to Imgur
Asked Answered
C

2

10

I tried to use jQuery Dropzone to upload an image to Imgur or any other domain but that's not working.

This is my dropzone setup:

$("div.dropzone").dropzone
  success: -> console.log arguments
  paramName: "image"
  method: "post"
  maxFilesize: 2
  url: "https://api.imgur.com/3/upload"
  headers:
    Authorization: "Client-ID *************"

This doesn't work. It says that return code is 0. The request headers:

Host: api.imgur.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Origin: http://my.opencubes.io
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization,cache-control,x-requested-with
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache

First as you can see the cient id doesn't appear :(. But the big problem is that the method used is OPTIONS. The response headers:

headers

I have the same problem when I try to upload the file to another domain of mine (the dropzone is located in a subdomain)

In the console I see:

Une demande multi-origines (Cross-Origin Request) a été bloquée : la politique « Same Origin » ne permet pas de consulter la ressource distante située sur https://api.imgur.com/3/upload. Ceci peut être corrigé en déplaçant la ressource sur le même domaine ou en activant CORS.

Which can be translated by

A multi-origin request was blocked: the policy "Same origin" does not allow to see remote resource located in https://api.imgur.com/3/upload. this an be fixed by moving the resource on the samin domain or by enabling CORS.

Cajolery answered 21/8, 2014 at 13:29 Comment(6)
the OPTIONS request is a normal request: this is used to ask for permissions relative to CORS restrictions (see here for more info on CORS: toc-handling-a-not-so-simple-request). Is there only one request done to api.imgur.com ? Can you show us all request/response headers and content of requests to this endpoint ? Also, check out your console output to see if there's no problem. What are the data sent ? What's the value of the image name in the request ? If you really want to upload a file, you look into AJAX file upload handling (Google it).Lacrimatory
@Lacrimatory thank you. The problem is that there is indeed ONLY ONE REQUEST to imgur. And that's the only one. In the console it says that the request multi-origin was blocked and that I mus move it to the same domain :/. And I wanna use DropzoneCajolery
What's the message exactly ? What is the exact domain name where the request comes from and the domain name where it goes ?Lacrimatory
Indeed, I don't see any Access-Control-Allow-Origin header in the response so it might be related... But you can't change that anyway. That look strange this header is missing. Also, be sure to have the correct Content-Type as multipart/form-data to handle the file upload. Finally, here's the link I messed up in my previous comment: html5rocks.com/en/tutorials/cors/… Have a look to it to understand how CORS work, maybe you'll find some useful information in there. Edit: what's the exact domain name where the request comes from ?Lacrimatory
My mistake. I put the request headers in the response header in my question. Fixed. the domain is my.opencubes.io which does not exist but in my hosts fileCajolery
Let us continue this discussion in chat.Lacrimatory
L
28

The OPTIONS request is a normal request: this is used to ask for permissions relative to CORS restrictions. Have a look to this page to understand how CORS work under the hood.

In your case, this is a pure CORS related issue. The OPTIONS request contains this header:

Access-Control-Request-Headers: authorization,cache-control,x-requested-with

Which means: can I use "authorization", "cache-control" and "x-requested-with" headers in my cross-domain AJAX request ?

The response you get is the following:

Access-Control-Allow-Headers :"Authorization, Content-Type, Accept, X-Mashape-Authorization"

Which means: you're allowed to use those headers only: "Authorization", "Content-Type", "Accept", and "X-Mashape-Authorization".

As you can see, "cache-control" and "x-requested-with" are not listed in the allowed list, causing the browser to reject the request.

I've come to 2 test code sample which show this behavior:

Example 1 (working)

var data = new FormData();
data.append('image', 'http://placehold.it/300x500');

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.imgur.com/3/upload', true);
xhr.setRequestHeader('Authorization', 'Client-ID xxxxxxxxxx');
xhr.send(data);

Here are the preflight request's headers sent when running this code (as shown by Firefox 30 devtools, and I've removed unrelated headers such as User-Agent, Accept ...):

And the corresponding response's headers

  • access-control-allow-origin :"*"
  • Access-Control-Allow-Methods :"GET, PUT, POST, DELETE, OPTIONS"
  • Access-Control-Allow-Headers :"Authorization, Content-Type, Accept, X-Mashape-Authorization"

Here, we can see that we prompt access to the "authorization" header, and the server is accepting this header, allong with the POST method and any origin URL, so the CORS requirements are satisfied and the request is allowed by the browser.

Example 2 (not working)

var data = new FormData();
data.append('image', 'http://placehold.it/300x500');

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://api.imgur.com/3/upload', true);
xhr.setRequestHeader('Authorization', 'Client-ID xxxxxxxxxx');
// the only difference with the previous code is this line
xhr.setRequestHeader('Cache-Control', 'no-cache');
xhr.send(data);

Preflight request's headers:

Preflight response's headers (which is the same as in example 1):

  • access-control-allow-origin :"*"
  • Access-Control-Allow-Methods :"GET, PUT, POST, DELETE, OPTIONS"
  • Access-Control-Allow-Headers :"Authorization, Content-Type, Accept, X-Mashape-Authorization"

Here, the "Access-Control-Request-Headers" header prompt access for "cache-control", which the server does not provide, so the CORS requirements are not satisfied and the request is rejected by the browser.

Here's a JSFiddle referencing different working and not working demos for your problem: http://jsfiddle.net/pomeh/Lfajnebh/. Pay attention to details to understand what's going on, there is few comments but they are here to emphasis trickiest parts of the code.

As a bonus, I've sent a pull request to DropZone's GitHub repository to fix this problem (https://github.com/enyo/dropzone/pull/685) which allows you to remove pref-defined headers by DropZone. Give it a try:

var myDropzone = new Dropzone('.dropzone', {
    //...
    headers: {
        'Authorization': authorizationHeader,
        // remove Cache-Control and X-Requested-With
        // to be sent along with the request
        'Cache-Control': null,
        'X-Requested-With': null
    }
});

The code above should work with my patched version (https://github.com/pomeh/dropzone/commit/f0063db6e5697888582421865840258dec1ffdc1), whereas the code above should not:

var myDropzone = new Dropzone('.dropzone', {
    //...
    headers: {
        'Authorization': authorizationHeader,
        // remove Cache-Control and X-Requested-With
        // to be sent along with the request
    }
});
Lacrimatory answered 26/8, 2014 at 0:1 Comment(1)
Adding headers 'Cache-Control': null, 'X-Requested-With': null is working fineSloe
R
2

You're running into the browser's same-origin security policy. Every browser has one; "origin" basically means "the same site". my JavaScript on example.com can access whatever it likes on example.com, but it's not allowed to read anything from demonstration.com, example.net, or api.example.com. They're from a different origin. Here's a table of what counts as the same origin.

Without it, I could write a web page that steals all your gmail and private Facebook photos. My malicious JavaScript would make web requests to gmail.com and facebook.com, find the links to your emails & photos, load that data too, and then send it off to my own server.

But some services, like APIs, are designed to be accessed by other services. That's where CORS comes in - Cross-Origin Resource Sharing. Web services can use CORS to tell browsers that it's fine to allow access from scripts. If you want to test your code by submitting to your own server, make sure your server is sending the required HTTP response headers.

If you're developing locally, you must also be sure to test from a web server - an address beginning with http://, not file://. The protocol is part of the origin, so you can't submit to an http endpoint from a file URL.


CORS has different types of requests. Some requests are considered simple requests, but others - requests with custom headers - require "preflighting". This means the browser will send a request to the server saying "Is this request OK for CORS?" using the HTTP OPTIONS method before sending the actual request. Any request with custom headers requires preflighting; that's where your HTTP OPTIONS is coming from. jQuery adds a custom X-Requested-With header to AJAX requests, so even if you hadn't added those you'd still see that Options request before the actual POST.

From your screenshots, it looks like Imgur is going to allow your HTTP POST method. Let's move on to figuring out why that's not working.


We're using the Imgur image upload API endpoint. This has one required parameter (image), and if we only want anonymous uploads all we need is a registered app. The upload method lets us send a simple URL to an image for upload, so let's try making an AJAX request to Imgur:

$.ajax
  success: (data) -> console.log data
  type: "POST"
  data: 
    image: "http://placehold.it/300x500"
  url: "https://api.imgur.com/3/upload"
  headers:
    Authorization: "Client-ID *************" # Don't forget to put your actual Client-ID here!

The next step is to try using the Filereader API to read the file from the form, and send that. Here's a CoffeeScript submit handler for that:

$('#file-form').submit (ev) -> 
  ev.preventDefault()
  file = $('#file-form input[name=file]').get(0).files[0] 
  $.ajax
    success: (data) -> console.log data
    type: "POST"
    data: 
      image: file
    url: "https://api.imgur.com/3/upload"
    headers:
      Authorization: "Client-ID *************"

Finally, we can try using Dropzone to achieve the same thing:

$("div.dropzone").dropzone
  success: (file, response) -> 
    console.log file
    console.log response
  paramName: "image"
  method: "post"
  maxFilesize: 2
  url: "https://api.imgur.com/3/upload"
  headers:
    "Authorization": "Client-ID *************"

The Dropzone success callback gets two arguments: the file that was uploaded, and the response from the server. You'll probably be most interested in the latter; Imgur sends back an id and a link parameter on success that you can use to show the user their newly-uploaded image.


There's an example project for using the Imgur API from JavaScript available on Github here.

Rase answered 25/8, 2014 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.