CORS not working in Django but settings seem correct

Viewed 7336

I am trying to make a POST call to Django from a React Native Web front end on different subdomains.

I thought I had configured CORS correctly, but that does not seem to be the case.

Here's what my Django settings.py looks like:

CORS_ALLOW_CREDENTIALS = True

CORS_ALLOW_HEADERS = ['*']

CORS_ALLOWED_ORIGINS = ['https://api.example.com', 'https://example.com', 'https://www.example.com' ]

CSRF_TRUSTED_ORIGINS = [
    'https://api.example.com', 'https://example.com', 'https://www.example.com'
]

ALLOWED_HOSTS = ["0.0.0.0", "api.example.com", "example.com"]

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

INSTALLED_APPS = [
     ...
    'corsheaders',
     ...
]

What exactly am I doing wrong here? The error I'm getting is this:

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

And this is my Django view:

class PageCreateView(generics.CreateAPIView):
    queryset = Page.objects.all()
    serializer_class = PageSerializer

What could be causing this? Am I missing some setting in React? I'm using axios to make the calls, with the only header being "Content-Type": "application/json"

EDIT: Could this be due to some nginx rule on my server? Or maybe my Kubernetes configuration? I am using Docker to set it up the container and can easily link the Dockerfile, or any information from my Kubernetes setup

12 Answers

I had this issue before and I suggest to use:

CORS_ORIGIN_ALLOW_ALL = True   

that will allow all origins

with this config ofcourse:

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]

INSTALLED_APPS = [
     ...
    'corsheaders',
     ...
]

I think, this could be resulted from the sequence of middlewares in the settings. (I am not completely confident though)

Can you try the following sequence of middleware

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

and just remove

CORS_ALLOW_HEADERS = ['*']

from the settings.

Let me know if this works out

I have had a boatload of CORS issues with Django. Generally, you might try to use:

CORS_ALLOWED_ORIGINS = ['*']

CSRF_TRUSTED_ORIGINS = ['*']

(Note: This is just boilerplate and you probably don't want to do it in production; hunting down the actual issue is a necessity in the end)

to make sure it's in your Django setup. However, as I have mentioned here: What can cause Chrome to give an net::ERR_FAILED on cached content against a server on localhost? There are all sorts of things that can cause CORS errors (sometimes not even registering them as CORS errors) and it can show up coming from all sorts of locations. The real issue is best hunted by digging through your logs. (Note: this answer shows how to put your Django logs into files, which you probably should be doing. The standard Django REST-response startup logs will contain useful information)

Step one is to see if a specific request is hitting your Django logs at all. If it is, your CORS settings within Django are the problem. You can easily tell why it's getting rejected because Django will have the fully qualified (MYSUBDOMAIN.example.com) domain that it has rejected in the log. This makes changing your Django settings a snap; simply add the rejected domain to your allowed origins.

Note: If you're not a BASH / Django pro, going to the log directory (should be referenced in your settings.py) where Django is stuffing its logs and executing ls -alrt will give you the last modified log.

I have always debugged this problem in Apach2 on Ubuntu, though. So I had a log at /var/log/apache2 that showed me exactly which domain was being rejected and why. (When Apache rejected the request; if you're putting a web server in front of Django it will also probably have CORS policies and plugins) If you're using ngnix on docker, the following question (Where are logs of docker nginx conainter stored in host) suggests you use this command line command to get the log files out of docker and see what they're doing.

docker inspect --format='{{.LogPath}}' <nginx-containter-id>

I personally will often end up booting a Docker instance from a command line and going to the BASH prompt to debug it. Advice on that is outside the scope of this question.

Going out a step further, it is possible that this CORS policy problem is even farther out on your network stack. There are times when misconfiguration of security policies, bad redirects, or other infrastructure-related issues are misunderstood by Chrome as CORS issues. A CORS interpretation can even be a browser configuration issue, in some cases. I, on occasion, have booted up a virtual machine to set my security levels in Chrome to the absolute minimum and try it, just to see if CORS was really the issue.

Sometimes it wasn't. Sometimes it was that something else - a load balancer, a firewall, a misconfigured AWS security group - was the culprit.

So, yeah, digging this out can be a full on bug safari rather than simple hunt. But the core thing to check first is whether your Django logs are spitting out anything after each REST request. If they're not you need to go up the chain because you're not talking to Django at all.

Ah, another note: We get CORS errors when Django can't write to its log files.

I had a similar issue which was solved by shifting corsheaders.middleware.CorsMiddleware above django.middleware.common.CommonMiddleware,in MIDDLEWARE array (settings.py).

Disclaimer: not a django dev.

But you are using a non-whitelisted value for Content-Type which means your POST request will be "preflighted". Can you confirm the OPTIONS request is properly handled by your API?

If you're running this in K8s, see if including the following to ALLOWED_HOSTS helps:

gethostname(), gethostbyname(gethostname())

I did something very similar to what you're doing and this configuration worked for me:

INSTALLED_APPS = [
    ...
    'corsheaders',
    ...
]

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.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

CORS_ORIGIN_WHITELIST = [
     'https://website.com'
]

Can you try:

CORS_ORIGIN_WHITELIST = ( 'https://example.com' )

CORS_ALLOW_CREDENTIALS = True makes CORS_ALLOW_HEADERS = ['*'] be translated literally i.e. not as wildcard.

If you want to add custom headers you need to extend the default list as it's done in the doc;

from corsheaders.defaults import default_headers

CORS_ALLOW_HEADERS = list(default_headers) + [
    'my-custom-header',
]

As the request has Content-Type of application/json this is not a simple request as per MDN. So this request triggers CORS preflight request which includes Access-Control-Request-Headers request header. This request header elicits Access-Control-Allow-Headers response header.

Value of this response header can be * to indicate wildcard value in the absence of credentials. But the * is interpreted literally when HTTP cookies or authentication information are present in the request as it's done here with CORS_ALLOW_CREDENTIALS = True.

Here in CORS preflight Access-Control-Request-Headers has value of Content-Type but Access-Control-Allow-Headers is a literal * because of CORS_ALLOW_HEADERS = ['*']. So the request is not authorized, that's why the error No 'Access-Control-Allow-Origin' header is present on the requested resource.

GET works here because it is a "simple request" and doesn't trigger CORS perflight.

CORS
Access-Control-Allow-Headers

I think its possible that you are missing a couple middlewares:

django.middleware.csrf.CsrfViewMiddleware
corsheaders.middleware.CorsPostCsrfMiddleware

otherwise I believe there is no problem with your CORS_ALLOWED_ORIGINS and CSRF_TRUSTED_ORIGINS settings

Are you sure that CORS_ALLOW_HEADERS allows you to set '*'?

Based on here, it looks like it is designed to be extended rather than replaced: https://github.com/adamchainz/django-cors-headers/blob/2d22268ff59aa0b79820ea5b8983efd1e514ec4c/README.rst#cors_allow_headers This test also led me to think that it is required to be extended rather than replaced https://github.com/adamchainz/django-cors-headers/blob/5067faefeb22beffcf1b12dd7ad9d3fb6d4b26e4/src/corsheaders/checks.py#L20-L21

I would suggest trying that with the default or try extending it from the above link

I believe that is a library-specific value and not a browser or request CORS value

edit: added test link to github.com

As explained here, The CORS Access-Control-Allow-Origin line expects in one of these two formats:

Allow everything: probably not what you want

Access-Control-Allow-Origin: *

Allow a single specific origin:

Access-Control-Allow-Origin: [SCHEME]://[HOST]:[PORT_OPTIONAL]

Scheme: AKA protocol, this can be HTTP, HTTPS . Host: This has to exactly match the requesting origin, so subdomain.domain.com or domain.com Port: [Optional, but important for you] Leaving the port out is the same as putting default port :80 if your scheme is HTTP and :443 if your scheme is HTTPS.

Also, The server https://example.com which you are making the request to has to implement CORS to grant Script from your website access. Your Script can't grant itself permission to access another website.

I used a CORS extension named allow-cors-access-control from chrome web store.

Or, You can use a change-origin chrome plugin like this:

Moesif Orign & CORS Changer

You can make your local dev server (ex: localhost:8080) to appear to be coming from 172.16.1.157:8002 or any other domain.

Related