CORS not working in Django but settings seem correct
Asked Answered
A

15

14

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

Adduction answered 30/4, 2021 at 3:1 Comment(14)
did you solve this problem?Gametocyte
@MahmoudAdel I have not, noAdduction
I thought that it may be better if someone has a better answer, I had this issue before but got it fixed by setting my conf correctly, I don't know why yours is not working, but all I can do is sharing my Django conf, and for react it may be a missing header issueGametocyte
@MahmoudAdel Yeah, I suspect a missing header too. I can put a bounty on this in two hours, so I'll probably end up doing thatAdduction
maybe this answer can help? https://mcmap.net/q/158103/-axios-having-cors-issueGametocyte
@MahmoudAdel Yeah, it might be due to some nginx ruleAdduction
What is the Origin header and request method of the request that axios sends?Myrlmyrle
My only header is "application/json" and the method that is having trouble is POST. GET has no issue. I haven't explicitly tested PUT or PATCH, though I can if need beAdduction
What’s the HTTP status code of the response? You can use the Network pane in browser devtools to check. If Chrome doesn’t show it to you, use the Network pane in Firefox devtools to check it. Is it a 4xx or 5xx error rather than a 200 OK success response?Brunei
@Brunei Actually, the CORS issue seems to be gone. I'm not entirely sure how, as I haven't changed anything in my setup. I'll go back over this and if I have issues I'll tag you.Adduction
"My only header is "application/json" and the method that is having trouble is POST.". No it isn't. That's the only header you passed to Axios, but the request the browser actually sends has a lot more. Take a look at your network window.Myrlmyrle
Can you post the headers your server actually sent out by running something like http head https://api.example.com/api/v1/pagescreate/? The http command comes with the httpie package. Also, since you mentioned you are using nginx, can you post the output from making the request directly against your django server and also after it going through nginx?Northwest
Are you using the CSRF token (ie. cookies)? Or JWT?Melloney
When you say React Native Web front end, is it a native app? The security model for XMLHttpRequest is different than on web as there is no concept of CORS in native apps.Eleonoreleonora
F
7

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

Farleigh answered 7/5, 2021 at 5:37 Comment(2)
I tried EVERYTHING to get this to work, but your suggestion or rearranging the sequence of how the middleware is loaded actually got this working for me! Thank you!Sourpuss
For me, only one api (rest were working fine) was causing the issue. It was very frustrating but rearranging the sequence has resolved the issue.Abernethy
J
5

Be sure to have this configuration in your settings with the right order:

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

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

For production:

from corsheaders.defaults import default_headers

CORS_ALLOW_HEADERS = default_headers + (
    "custom-headers",
   
)

CORS_ORIGIN_WHITELIST = ("http://localhost:8000", "https://Your frontend host")

For non-production:

CORS_ORIGIN_ALLOW_ALL = True   

that will allow all origins

Jibheaded answered 4/5, 2021 at 11:32 Comment(1)
While this is a fine answer in the short run, it does allow cross site scripting, which is what CSRF is supposed to prevent. Digging into your logs and finding out exactly what the qualified domain you were rejecting is, then adding those domains to your allowed origins won't leave a gaping security hole.Avens
P
2

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

Perspicuity answered 9/5, 2021 at 1:38 Comment(0)
A
1

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.

Avens answered 7/5, 2021 at 21:42 Comment(1)
No idea why this got downvoted. It replicates some of the other answers, but they are not complete in that they do not cover that CORS is often an inaccurate label. I've spent weeks of time digging into things Chrome called CORS issues in Django; they're often not CORS or Django but buried in the OS somewhere.Avens
L
1

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

Lanthorn answered 21/11, 2021 at 21:55 Comment(1)
Perhaps you can put your answer it in code. This will be more readable and usable for other users.Scenic
S
0

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?

Semmes answered 4/5, 2021 at 16:1 Comment(0)
D
0

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

gethostname(), gethostbyname(gethostname())
Diarist answered 6/5, 2021 at 16:46 Comment(0)
F
0

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'
]
Foin answered 6/5, 2021 at 22:7 Comment(0)
S
0

Can you try:

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

Shena answered 9/5, 2021 at 0:44 Comment(0)
R
0

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

Rehabilitate answered 14/12, 2021 at 11:23 Comment(1)
-Do we need to maintain any order here?Aversion
F
0

In my case, I am using HTMX + hx-boost, and htmx was sending an AJAX request instead of redirecting the browser to login to google. Once I set hx-boost="false" on the button that redirects to login with google, it worked!

<body hx-boost="true">

<!-- Your page content... -->

<!-- Login w/ Google -->
<form action="{% provider_login_url 'google' %}" method="post" hx-boost="false">
    {% csrf_token %}
    <button type="submit">
        Login w/ Google
    </button>
</form>

</body>
Funkhouser answered 11/7 at 19:22 Comment(0)
F
0

in my case adding some of the special headers fixed the issue

    CORS_ALLOW_HEADERS = default_headers + (
    "cache-control",
    "custom-headers",
    "Sec-Ch-Ua-Platform",
    "Sec-Ch-Ua-Mobile",
    "Sec-Ch-Ua",
    "Referer",
)

these headers are found in the headers tab inside the network in the inspect option.

enter image description here

your headers might be different from this

Forevermore answered 18/7 at 5:44 Comment(1)
Doesn’t CORS_ALLOW_HEADERS = ['*'] already cover this case?Asha
P
-1

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

Pasley answered 2/5, 2021 at 12:42 Comment(1)
['*'] is a sequence (subtype: list) of 1 string.Myrlmyrle
L
-1

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.

Lassitude answered 2/5, 2021 at 13:4 Comment(0)
P
-2

Make sure to restart the server after adding the above configuration and clear the cache and hard reload. It worked for me.

Phlyctena answered 26/10, 2022 at 13:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.