4

I have created a backend server in Django with Django Rest Framework, and a React frontend. My front retrieves data from the back through APIs. Each app is on a different subdomain of the same domain. I use Cloudflare to manage DNS and for SSL / security.

I have had no problem with GET calls. For POST calls, I send the POST data to the server through a form, and I know it works as there is a change to the database (record created in this instance). However, I have implemented a 'retry until' function using axios and polly-js. This method waits until it receives a 201 CREATED response, otherwise retries.

My problem is that when I submit the form on React, the POST is indeed received and processed by my backend server, but the response is blocked. So after 10-15 seconds, I receive an error message through the console and my 'retry until' method sends another POST request. The response of this second one is not blocked by Chrome, and I receive the 201 status. But the overall effect is that I have now 2 identical records in the database because the first call did not 'receive' the response and retried.

The error in the console I get is:

Access to XMLHttpRequest at 'https://subdomain.domain.io/' from origin 'https://api.domain.io' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. 

What I have already done and has not worked:

  • I don' think it's a backend problem as the POST goes through and the record created. But I have whitelisted all CORS origins in Django
  • I have added the header 'Access-Control-Allow-Origin': '*' to my POST request through axios
  • I have manually added the same 'Access-Control-Allow-Origin': '*' header from my Django DRF response.

Both request that I send (first one through form submission, second one through automatic retry) are identical (seen through Chrome network tab):

Accept: application/json, text/plain, */*
Content-Type: application/json;charset=UTF-8
Origin: https://subdomain.domain.io
Referer: https://subdomain.domain.io/path
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36

My POST and retry method:

const postData = (url, data, headers) => {

    headers['Access-Control-Allow-Origin'] = "*"

    return polly()
      .waitAndRetry([100, 200, 400, 1000])
      .executeForPromise(async () => {
        const rsp = await axios.post(url, data, headers);

        if (rsp.status < 210) {

          return rsp.data;

        }

        return Promise.reject(rsp);
      });
  };

The response I get when the second try succeeds:

access-control-allow-origin: *
allow: GET, POST, HEAD, OPTIONS
cf-ray: 4dd7cbccce256948-CDG
content-length: 364
content-type: application/json
date: Mon, 27 May 2019 11:54:47 GMT
expect-ct: max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"
server: cloudflare
status: 201
strict-transport-security: max-age=2592000; includeSubDomains; preload
vary: Accept, Origin
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN

For reference, the CORS settings in Django

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django_otp.middleware.OTPMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]


CORS_ORIGIN_ALLOW_ALL = True


CORS_ALLOW_HEADERS = (
    'accept',
    'accept-encoding',
    'authorization',
    'content-type',
    'dnt',
    'origin',
    'user-agent',
    'x-csrftoken',
    'x-requested-with',
    'access-control-allow-origin'
)

EDIT

Firefox shows me the response for the 504 GATEWAY TIMEOUT of the first POST request:

cf-ray: 4dd82ac15f42cd97-CDG
content-type: text/html; charset=UTF-8
date: Mon, 27 May 2019 13:00:36 GMT
expect-ct: max-age=604800, report-uri="ht….com/cdn-cgi/beacon/expect-ct"
expires: Thu, 01 Jan 1970 00:00:01 GMT
pragma: no-cache
server: cloudflare
set-cookie: __cfduid=d0a3a9ee872171ada14cb…n=.wisly.io; HttpOnly; Secure
set-cookie: cf_use_ob=0; path=/; expires=Mon, 27-May-19 13:01:06 GMT
strict-transport-security: max-age=2592000; includeSubDomains; preload
x-content-type-options: nosniff
X-Firefox-Spdy: h2

The Access-Control-Allow-Origin is missing, but it is part of my backend code. Could anything be happening with Cloudflare?

The expected result would be that, when I POST through the form, receive the 201 back (which would be accepted and read by Chrome) so that I can

  • Show the user the form has been correctly saved to the database
  • Not retry the POST, resulting in dual entry.

Thank you!

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
AlexM88
  • 194
  • 3
  • 14
  • 1
    Don't add `access-control-allow-origin` in Axios – it's not a client header. – AKX May 27 '19 at 12:41
  • Thanks, that is true. Removing it does not fix the problem though it seems. – AlexM88 May 27 '19 at 12:42
  • You must provide proper response for OPTIONS request FIRST, see network tab, google for hints – xadm May 27 '19 at 12:51
  • What’s the HTTP status code of the response in the case where you see the *No 'Access-Control-Allow-Origin' header is present* message? Use the Network pane in browser devtools to check. Is it a 4xx or 5xx error instead of 2xx success message? – sideshowbarker May 27 '19 at 12:55
  • @xadm Ah ! Actually this is probably the issue the first request does not send OPTIONS. How can I 'force' this ? The other requests automatically send OPTIONS before. There was a typo in my code I had changed it to check for responses below 210, so the 200 for options did work. – AlexM88 May 27 '19 at 12:55
  • @sideshowbarker the ones that work have an OPTIONS with 200, and a POST with 201. My actual code checks for anything below 210 as a pre-production step. – AlexM88 May 27 '19 at 12:56
  • The lack of an OPTIONS preflight isn’t a problem. Trying to force the browser to send an OPTIONS preflight isn’t going to fix whatever actual problem you’re having. – sideshowbarker May 27 '19 at 13:00
  • probably you don't need polly - see [this answer](https://stackoverflow.com/a/55879318/6124657) – xadm May 27 '19 at 13:26

2 Answers2

0

You don't need to put access-control-allow-origin in your CORS_ALLOW_HEADERS.
The Django CORS will add automatically on requests based on your configurations.

Some tips you could try:

  • Use default_headers instead of put all default headers (just to improve the code)
  • I already had problems with some basic headers that Chrome was sending and my app didn't allow them, so I had to add
from corsheaders.defaults import default_headers

CORS_ALLOW_HEADERS = default_headers + (
    'Cache-Control', 'If-Modified-Since',
)
  • Try adding "trusted origins"
CSRF_TRUSTED_ORIGINS = (
    '*.yourdomain.com',
)
Danilo Akamine
  • 705
  • 7
  • 13
  • Thanks for the tips! I tried all this but still have the same issue. Actually Safari and Firefox have the same behavior. Once the 'first' request passes, the rest work well. Usually I need to close the browser and clear cache for the problem to happen again. – AlexM88 May 27 '19 at 13:47
0

Check out this lib. https://pypi.org/project/django-cors-headers. It helped me to solve the same problem with React.