I have a Django website at http://example.com that works fine, including post requests. I've added HTTPS so my site is accessible at https://example.com too.
I can load any page on HTTPS, but I always get CSRF validation errors when I try to POST. POST requests work fine on HTTP.
My Django process is running with gunicorn behind nginx, and I have nginx setting X_Forwarded_For
. So, an HTTPS request has the following headers (taken from request.META
):
'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com:80',
'HTTP_REFERER': 'https://example.com/user/delete/49/',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'https',
'HTTP_X_REAL_IP': '1.2.3.4',
and an HTTP request has the following headers:
'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'HTTP_ACCEPT_ENCODING': 'gzip, deflate',
'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.5',
'HTTP_CONNECTION': 'close',
'HTTP_COOKIE': 'redacted',
'HTTP_HOST': 'example.com.com:80',
'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:24.0) Gecko/20100101 Firefox/24.0',
'HTTP_X_FORWARDED_FOR': '1.2.3.4, 192.168.252.22',
'HTTP_X_FORWARDED_PROTO': 'http',
'HTTP_X_REAL_IP': '1.2.3.4',
Why doesn't CSRF work on HTTPS when I have no problems over HTTP?
The CSRF middleware is activated by default in the MIDDLEWARE setting. If you override that setting, remember that 'django.middleware.csrf.CsrfViewMiddleware' should come before any view middleware that assume that CSRF attacks have been dealt with.
The CSRF token cookie is named csrftoken by default, but you can control the cookie name via the CSRF_COOKIE_NAME setting. The above code could be simplified by using the JavaScript Cookie library to replace getCookie: The CSRF token is also present in the DOM in a masked form, but only if explicitly included using csrf_token in a template.
Rather than adding CsrfViewMiddleware as a blanket protection, you can use the csrf_protect () decorator, which has exactly the same functionality, on particular views that need the protection. It must be used both on views that insert the CSRF token in the output, and on those that accept the POST form data.
Turns out it was an nginx configuration issue. My server setup is:
nginx -> nginx -> gunicorn
On the second nginx system, I had
proxy_set_header Host $host:$server_port;
However, since HTTPS is terminated at the first nginx, $server_port
was always 80.
On HTTPS, Django does strict referer checking (see point 4). Looking at the Django source:
good_referer = 'https://%s/' % request.get_host()
if not same_origin(referer, good_referer):
reason = REASON_BAD_REFERER % (referer, good_referer)
logger.warning('Forbidden (%s): %s', reason, request.path,
extra={
'status_code': 403,
'request': request,
}
)
return self._reject(request, reason)
CSRF validation was failing because "https://example.com/" != "https://example.com:80/"
.
Like mentioned here I solved this problem by adding the following Django constants in settings.py
so Django considers proxy headers:
# Setup support for proxy headers
USE_X_FORWARDED_HOST = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With