Hey everyone,
a django project I deployed in production gives me some headaches. I set up csrf tokens for a form post and it works if used from a desktop browser, but if the post is performed with a mobile android browser through a submit button, I get the following error:
Forbidden (403)
CSRF verification failed. Request aborted.
Help
Reason given for failure:
Origin checking failed - https://<domain.com> does not match any trusted origins.
In general, this can occur when there is a genuine Cross Site Request Forgery, or when Django's CSRF mechanism has not been used correctly. For POST forms, you need to ensure:
Your browser is accepting cookies.
The view function passes a request to the template’s render method.
In the template, there is a {% csrf_token%} template tag inside each POST form that targets an internal URL.
If you are not using CsrfViewMiddleware, then you must use csrf_protect on any views that use the csrf_token template tag, as well as those that accept the POST data.
The form has a valid CSRF token. After logging in in another browser tab or hitting the back button after a login, you may need to reload the page with the form, because the token is rotated after a login.
You're seeing the help section of this page because you have DEBUG = True in your Django settings file. Change that to False, and only the initial error message will be displayed.
You can customize this page using the CSRF_FAILURE_VIEW setting.
settings.py
from <project_name>.settings import *
DEBUG = True
SECRET_KEY = '<secret_key>'
ALLOWED_HOSTS = ['<domain.com>','www.<domain.com>']
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
CSRF_TRUSTED_ORIGINS = [
'https://www.<domain.com>',
'http://www.<domain.com>'
]
INSTALLED_APPS = [
'<app_name>',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
MIDDLEWARE = [
'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',
]
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s: '
'%(message)s',
}
},
'handlers': {
'file': {
'class': 'logging.handlers.'
'TimedRotatingFileHandler',
'filename': '/var/log/<project_name>/'
'<project_name>.log',
'when': 'midnight',
'backupCount': 60,
'formatter': 'default',
},
},
'root': {
'handlers': ['file'],
'level': 'INFO',
},
}
CACHES = {
'default': {
'BACKEND': 'django.core.cache.backends.filebased.'
'FileBasedCache',
'LOCATION': '/var/cache/<project_name>/cache',
}
}
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': '<project_name>',
'USER': '<project_name>',
'PASSWORD': '<project_name>',
'HOST': 'localhost',
'PORT': 5432,
}
}
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from main_app.models.poll_model import Poll
from main_app.forms.poll_form import PollForm
from django.forms import modelformset_factory
from django.views.decorators.csrf import csrf_protect, ensure_csrf_cookie
# Create your views here.
def index(request):
return render(request, '<app_name>/movies.html')
def movies_view(request):
return render(request, '<app_name>/movies.html')
@csrf_protect
@ensure_csrf_cookie
def poll_view(request):
""" Poll View """
PollFormSet = modelformset_factory(Poll, form=PollForm, extra=0)
if request.method == "POST":
formset = PollFormSet(request.POST, request.FILES)
if formset.is_valid():
formset.save()
return HttpResponseRedirect('')
else:
formset = PollFormSet()
context = {'formset': formset}
return render(request, '<app_name>/poll.html', context)
poll.html
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block navbar %}
<li class="nav-item">
<a class="nav-link" href="/movies/">Movies</a>
</li>
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="/poll/">Poll</a>
</li>
{% endblock %}
{% block content %}
<br/>
<form method="post">
{% csrf_token %}
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>
{{ formset.management_form }}
{{ formset.non_form_errors.as_ul }}
<table id="formset" class="form table table-dark table-striped">
{% for form in formset.forms %}
{% if forloop.first %}
<thead>
<tr>
<th scope="col">User</th>
<th scope="col">Mo</th>
<th scope="col">Tu</th>
<th scope="col">We</th>
<th scope="col">Th</th>
<th scope="col">Fr</th>
<th scope="col">Sa</th>
<th scope="col">Su</th>
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %}">
<td> {{ form.instance }} </td>
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Safe" class="btn btn-secondary">
</form>
{% endblock %}
The project runs on a Webserver with Nginx and Gunicorn with Ubuntu 20.04. For the encryption, “Let’s encrypt” is used.