Open SylvainBigonneau opened 1 year ago
The response has a 403 status code. Where is that coming from? Is it from a middleware above the OCRS middleware?
Also, you have a CORS-ish cookie:
< set-cookie: AWSALBCORS=xxxxxxx; Expires=Tue, 23 May 2023 13:12:04 GMT; Path=/; SameSite=None; Secure
Is your AWS ALB (application load balancer) controlling CORS for you? Perhaps it is stripping request CORS headers.
Encountering the same issue, I have everything set up in my settings.py
and it works fine on a single EC2 but when put behind an ALB the headers just seem to vaporize.
Currently just trying to replicate the headers that this library would send in my top level Nginx config. Painful but I think I'm making progress.
Thank you for the quick response @adamchainz!
The response has a 403 status code. Where is that coming from? Is it from a middleware above the OCRS middleware?
Haha, yes, I figured this would be confusing, but my example just happened to be on a confidential route where I was unauthenticated. It should still return the proper cors headers though, right? Anyway, here are similar outputs from a route that returns 200:
< HTTP/2 200
< content-type: application/json
< content-length: 1745
< vary: Accept-Encoding
< date: Sat, 20 May 2023 14:18:04 GMT
< set-cookie: AWSALB=xxxx; Expires=Sat, 27 May 2023 14:18:04 GMT; Path=/
< set-cookie: AWSALBCORS=xxxx; Expires=Sat, 27 May 2023 14:18:04 GMT; Path=/; SameSite=None; Secure
< server: nginx/1.21.0
< vary: Accept, Cookie, Origin
< allow: GET, HEAD, OPTIONS
< x-frame-options: DENY
< x-content-type-options: nosniff
< referrer-policy: same-origin
< x-cache: Miss from cloudfront
< via: 1.1 xxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: xxxx
< x-amz-cf-id: xxxx
Also, you have a CORS-ish cookie:
< set-cookie: AWSALBCORS=xxxxxxx; Expires=Tue, 23 May 2023 13:12:04 GMT; Path=/; SameSite=None; Secure
Is your AWS ALB (application load balancer) controlling CORS for you? Perhaps it is stripping request CORS headers.
As I mentionned at the end of my issue, I have no issues receiving the proper headers when I set them manually using finalize_response
:
def finalize_response(self, request, *args, **kwargs):
response = super(PublicObjectDetail, self).finalize_response(
request, *args, **kwargs
)
response["Access-Control-Allow-Origin"] = "*"
response[
"Access-Control-Allow-Headers"
] = "Origin, X-Requested-With, Content-Type, Accept"
return response
Result from curl:
< HTTP/2 200
< content-type: application/json
< content-length: 1745
< vary: Accept-Encoding
< date: Sat, 20 May 2023 14:05:12 GMT
< set-cookie: AWSALB=xxxx; Expires=Sat, 27 May 2023 14:05:12 GMT; Path=/
< set-cookie: AWSALBCORS=xxxx; Expires=Sat, 27 May 2023 14:05:12 GMT; Path=/; SameSite=None; Secure
< server: nginx/1.21.0
< vary: Accept, Cookie
< allow: GET, HEAD, OPTIONS
< access-control-allow-origin: *
< access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept
< x-frame-options: DENY
< x-content-type-options: nosniff
< referrer-policy: same-origin
< x-cache: Miss from cloudfront
< via: 1.1 xxxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: xxxx
< x-amz-cf-id: xxxx
As you can see, I receive the headers in the response. (by the way @neverabsolute , you should try this workaround to check if your issue is indeed due to outside factors such as the load balancer, in which case yours is probably not the same issue as mine)
I was asking if the ALB is stripping CORS request headers. Your workaround sets respnse CORS headers unconditionally, but that’s not a generally secure way to do CORS, hence this package only sets them on CORS requests. You could test by debugging the headers received by Django, or read up the AWS docs - I’ve found them mostly complete, if verbose.
My issue somehow ended up being related to me changing my authentication session engine to redis, spent 60+ hours debugging other stuff vs checking that 💀
I was asking if the ALB is stripping CORS request headers. Your workaround sets respnse CORS headers unconditionally, but that’s not a generally secure way to do CORS, hence this package only sets them on CORS requests. You could test by debugging the headers received by Django, or read up the AWS docs - I’ve found them mostly complete, if verbose.
Ah, my bad, I wrongly assumed you meant response headers instead, sorry!
I did read up a good sum of the AWS docs on this issue, and all they seem to talk about as far as I can see are the Origin
, Access-Control-Request-Method
, Access-Control-Request-Headers
headers. As mentioned in the first post, I already checked that Origin
was properly forwarded, but here is the same debugging including the other two:
from corsheaders.signals import check_request_enabled
from django.conf import settings
def cors_allow_api_to_everyone(sender, request, **kwargs):
print("CHECKING REQUEST ORIGIN")
print(request.headers['Origin'])
print(request.headers['Access-Control-Request-Method'])
print(request.headers['Access-Control-Request-Headers'])
return False
check_request_enabled.connect(cors_allow_api_to_everyone)
When requesting a url using curl this way:
curl -H "Origin: example.com" -H "Access-Control-Request-Method: GET" -H "Access-Control-Request-Headers: X-Requested-With" -v "https://myapp.com/api/myprivateroute/" -X OPTIONS
Here is the output in the server logs:
CHECKING REQUEST ORIGIN
example.com
GET
X-Requested-With
So I am thinking this proves that the headers are being forwarded correctly?
🤷♂️ I'm sorry, I'm not really sure then.
Check for a missing CSRF token!
@SylvainBigonneau Did you find the solution to this problem? I have got the same issue
Django==4.2.5 django-cors-headers==4.3.0 djangorestframework==3.14.0
All setting done as per the docs:
INSTALLED_APPS = [.., 'corsheaders', ..]
MIDDLEWARE = [..., 'corsheaders.middleware.CorsMiddleware',...]
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_HEADERS = default_headers
no sign of 'Access-Control-Allow-Origin' header when I do a curl:
P.S: The Django application is behind an nginx server with proxy pass set:
location /api{ proxy_pass http://backend:8000; }
@SylvainBigonneau Did you find the solution to this problem? I have got the same issue
Django==4.2.5 django-cors-headers==4.3.0 djangorestframework==3.14.0
All setting done as per the docs:
INSTALLED_APPS = [.., 'corsheaders', ..] MIDDLEWARE = [..., 'corsheaders.middleware.CorsMiddleware',...] CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_HEADERS = default_headers
no sign of 'Access-Control-Allow-Origin' header when I do a curl:
P.S: The Django application is behind an nginx server with proxy pass set:
location /api{ proxy_pass http://backend:8000; }
Facing same problem. Please let us know if you find a solution for this.
Any update on this? i have a similar issue
For anyone who finds this, the screenshot below shows that the Origin header is missing from the request. As specified in the issue 901, you need to add that header to get the access-control-allow-origin
header back,
@SylvainBigonneau Did you find the solution to this problem? I have got the same issue
Django==4.2.5 django-cors-headers==4.3.0 djangorestframework==3.14.0
All setting done as per the docs:
INSTALLED_APPS = [.., 'corsheaders', ..] MIDDLEWARE = [..., 'corsheaders.middleware.CorsMiddleware',...] CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_HEADERS = default_headers
no sign of 'Access-Control-Allow-Origin' header when I do a curl:
P.S: The Django application is behind an nginx server with proxy pass set:
location /api{ proxy_pass http://backend:8000; }
I think this issue has been solved!
Sorry for the ghosting people, I must admit I had given up hope on this, and left it up to my finalize_response
tweaks. I will give this another go today.
Nope, I can confirm this issue still occurs exactly the same on django-cors-headers v4.4.0:
> GET /api/objects/576/ HTTP/2
> Host: myapp.com
> User-Agent: curl/8.6.0
> Accept: */*
> Origin: example.com
> Access-Control-Request-Method: GET
> Access-Control-Request-Headers: X-Requested-With
>
< HTTP/2 403
< content-type: application/json
< content-length: 58
< date: Sat, 06 Jul 2024 14:40:10 GMT
< set-cookie: AWSALB=xxxx; Expires=Sat, 13 Jul 2024 14:40:09 GMT; Path=/
< set-cookie: AWSALBCORS=xxxx; Expires=Sat, 13 Jul 2024 14:40:09 GMT; Path=/; SameSite=None; Secure
< server: nginx/1.21.0
< vary: Accept, Accept-Language, Cookie, origin
< allow: GET, PUT, DELETE, HEAD, OPTIONS
< content-language: fr
< x-frame-options: DENY
< x-content-type-options: nosniff
< referrer-policy: same-origin
< x-cache: Error from cloudfront
< via: xxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: xxxx
< x-amz-cf-id: xxxx
<
* Connection #0 to host myapp.com left intact
{"detail":"Authentication error"}
Yes, I know it's a 403, because I didn't bother authenticating with curl, and I put all my public routes behind a Cloudfront cache because they are bombarded by requests from clients, so requesting those routes won't actually hit my server. But as far as I can tell, no matter the HTTP code, I should get that sweet sweet access-control-allow-origin
response header... and I don't :(
As usual, here is the debug output from the server logs, showing that the request does hit the server with all the proper request headers:
CHECKING REQUEST ORIGIN
example.com
GET
X-Requested-With
https://myapp.com,https://localhost:3000
And if I force the response headers by overriding the finalize_response
method of my route:
def finalize_response(self, request, *args, **kwargs):
response = super(ObjectDetail, self).finalize_response(
request, *args, **kwargs
)
response["Access-Control-Allow-Origin"] = "https://myapp.com,https://localhost:3000"
response["Access-Control-Allow-Headers"] = (
"Origin, X-Requested-With, Content-Type, Accept"
)
return response
I get the response header no problem, showing that it is not AWS stripping them off:
> GET /api/objects/576/ HTTP/2
> Host: myapp.com
> User-Agent: curl/8.6.0
> Accept: */*
> Origin: example.com
> Access-Control-Request-Method: GET
> Access-Control-Request-Headers: X-Requested-With
>
< HTTP/2 403
< content-type: application/json
< content-length: 58
< date: Sat, 06 Jul 2024 15:02:33 GMT
< set-cookie: AWSALB=xxxx; Expires=Sat, 13 Jul 2024 15:02:32 GMT; Path=/
< set-cookie: AWSALBCORS=xxxx; Expires=Sat, 13 Jul 2024 15:02:32 GMT; Path=/; SameSite=None; Secure
< server: nginx/1.21.0
< vary: Accept, Accept-Language, Cookie, origin
< allow: GET, PUT, DELETE, HEAD, OPTIONS
< access-control-allow-origin: https://myapp.com,https://localhost:3000
< access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept
< content-language: fr
< x-frame-options: DENY
< x-content-type-options: nosniff
< referrer-policy: same-origin
< x-cache: Error from cloudfront
< via: 1.1 xxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: xxxx
< x-amz-cf-id: xxxx
<
* Connection #0 to host myapp.com left intact
{"detail":"Authentication error"}
So nope, not solved!
By the way my app behaves, it really feels like django-cors-headers
is simply not "running" at all on my server, despite having installed the module (otherwise my debug signal would fail, since I'm importing the module to enable it), set the middleware before django's CommonMiddleware
, and setting CORS_ALLOWED_ORIGINS
properly (as it shows in my debugging).
I would love to help debugging this further, but I am not well versed in the intrications of debugging a third-party django middleware... If anyone can guide me a little on how and where I could look in my django app to investigate where the middleware is failing, I'd be happy to try it!
Could you clarify what you meant about the "missing CSRF token" @martinskou ? How would I check that from the request
object?
@SylvainBigonneau what happens if you send an OPTIONS http request? Like below:
curl -v -X OPTIONS ...
@SylvainBigonneau what happens if you send an OPTIONS http request? Like below:
curl -v -X OPTIONS ...
> OPTIONS /api/objects/576/ HTTP/2
> Host: myapp.com
> User-Agent: curl/8.6.0
> Accept: */*
> Origin: example.com
> Access-Control-Request-Method: GET
> Access-Control-Request-Headers: X-Requested-With
>
< HTTP/2 200
< content-type: text/html; charset=utf-8
< content-length: 0
< date: Sat, 06 Jul 2024 17:50:22 GMT
< set-cookie: AWSALB=xxxx; Expires=Sat, 13 Jul 2024 17:50:22 GMT; Path=/
< set-cookie: AWSALBCORS=xxxx; Expires=Sat, 13 Jul 2024 17:50:22 GMT; Path=/; SameSite=None; Secure
< server: nginx/1.21.0
< vary: origin
< x-cache: Miss from cloudfront
< via: 1.1 xxxx.cloudfront.net (CloudFront)
< x-amz-cf-pop: xxxx
< x-amz-cf-id: xxxx
<
* Connection #0 to host myapp.com left intact
I'd like to report that I'm having the same issues as seen here, spent yesterday working on first deploy to production, and ended up forcing the required headers into the response in some middleware. It's like django-cors-headers is just not being included...
API server
Pipfile (prod on DigitalOcean App Platform)
[requires]
python_version = "3.11"
[packages]
django = "==5.0.6"
psycopg = "==3.2.1"
python-dotenv = "==1.0.1"
tzdata = "==2024.1"
djangorestframework = "==3.15.1"
dj-rest-auth = "==6.0.0"
django-cors-headers = "==4.4.0"
django-allauth = "==0.61.1"
# Campaign Monitor API wrapper
createsend = "==7.0.0"
# WSGI HTTP server for Python (and Django) for Production usage
gunicorn = "==22.0.0"
# Static file handling for Django Admin
whitenoise = "==6.7.0"
sentry-sdk = {extras = ["django"], version = "*"}
INSTALLED_APPS = [
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework.authtoken',
'allauth',
'allauth.account',
'dj_rest_auth',
'dj_rest_auth.registration',
'corsheaders',
'app'
]
middleware
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'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',
'allauth.account.middleware.AccountMiddleware',
'app.middleware.InspectRequestMiddleware',
]
I had attempted to move corsheaders.middleware.CorsMiddleware
to different positions within the MIDDLEWARE stack but to no avail.
And same response as @SylvainBigonneau (no CORS headers being included).
Question about enablement of the library for a given response...
How this is block used within the CorsMiddleware, it would suggest that CORS is only enabled when the REGEX is set, or if signals are used?
def is_enabled(self, request: HttpRequest) -> bool:
return bool(
re.match(conf.CORS_URLS_REGEX, request.path_info)
) or self.check_signal(request)
Which goes on to be used within def add_response_headers(
and if it's not enabled, it simply returns the normal response.
Does this seem right? I'm not using signals nor regex, just basic configuration for setting the CORS_ALLOWED_ORIGINS.
Understanding CORS
Python Version
3.10
Django Version
3.2.9
Package Version
3.14.0
Description
Sorry for the very typical issue name, but I am in a real pickle here.
Here is my config:
environment variable:
DJANGO_ALLOWED_HOSTS=myapp.com,localhost:3000
settings.py:
For debugging, I also set up some hacky logging at request, using signals. handlers.py
When requesting a url using curl this way:
Here is the output in the server logs:
Here is the curl output for the response:
As you can see, no trace of any "Access-Control-Allow-Origin" response header there, despite the server receiving "example.com" as an origin, and the
CORS_ALLOWED_ORIGINS
settings being properly set.For further info, I tried forcing returning an "Access-Control-Allow-Origin" using the
finalize_response
override on one of my DRF methods, and it does return the header properly on the previously mentioned curl command. So it is likely not an issue of the header being lost on the way back to the client.