As Graphql is used as an API it is crucial to access it from different origins, such as an JS frontend from the browser which runs on a different port (or even IP).
When doing a request to another origin (e.g. from localhost:3000
(JS frontend) to localhost:8000
(django server)) the browser will verify the Access-Control-Allow-Origin
header in the response from the server and will verify if this matches our origin (as it is a security flaw if anybody can send us requests to which we respond) - if this header is not present or is missing our origin a CORS error will be raised.
In production you get around this by putting everything behind a reverse proxy like nginx, creating a unified origin, but during developing you can use https://github.com/adamchainz/django-cors-headers to add the Access-Control-Allow-Origin
header to our django HTTP responses which works with DRF or django admin (I did not get a CORS error accessing the admin site).
But how does the client know what is allowed and what is not allowed? Before sending a POST
request the client/browser will send an OPTIONS
request, known as an preflight request
This will respond with headers such as Access-Control-Allow-Origin
so the client/browser knows if its OK to POST
to the server.
For some reason the AsyncGraphQLView
will respond on the preflight OPTION
request with a 405 - Method Not Allowed
response which will lead to an CORS error.
The same issue also occured in the base library with FastAPI: #1942
Interestingly enough before updating to Firefox 106.0.2 I did not have this problem - Chrome results in the same error.
Trying to modify this
strawberry/strawberry/django/views.py
Lines 223 to 227 in 609ebc7
and redirecting to a simple/empty HttpResonse
in case of an OPTIONS
request coming in did not resolve the issue.
Here is some inspections using curl.
It works using django-cors-headers
on admin/
❯ curl -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost:8081" --head http://localhost:8000/admin/
HTTP/1.1 302 Found
Content-Type: text/html; charset=utf-8
Location: /admin/login/?next=/admin/
Expires: Sun, 30 Oct 2022 12:40:34 GMT
Cache-Control: max-age=0, no-cache, no-store, must-revalidate, private
X-Frame-Options: DENY
Content-Length: 0
Vary: Cookie, Origin
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://localhost:8081
but fails on graphql/
to set these headers
~
❯ curl -H "Access-Control-Request-Method: GET" -H "Origin: http://localhost:8081" --head http://localhost:8000/graphql/
HTTP/1.1 405 Method Not Allowed
Allow: GET, POST
Using the GET method for the API (which does not make use of a OPTIONS
request) also ignores set the headers via django-cors-headers
.
Maybe the root problem is here that Strawberry is using its own HttpResponse class
strawberry/strawberry/http/__init__.py
Lines 13 to 16 in 609ebc7
which is not subclassed from the Django HttpResponse.
I tried to force a django HTTP response by injecting return HttpResponse("hello")
into the first line of the function
strawberry/strawberry/django/views.py
Lines 223 to 224 in 609ebc7
but it was not picked up, although running an async dev server - right now I am out of ideas.
django==4.0.2
uvicorn[standard]==0.17.5
gunicorn==20.1.0
strawberry-graphql-django==0.5.1
strawberry-graphql[channels]<=0.132.0
strawberry-django-plus==1.27
django-cors-headers==3.13.0
Pay now to fund the work behind this issue.
Get updates on progress being made.
Maintainer is rewarded once the issue is completed.
You're funding impactful open source efforts
You want to contribute to this effort
You want to get funding like this too