I've deployed a Django application using Nginx and Gunicorn, and I'm encountering a "CSRF token missing" error specifically for POST requests. Here's a brief overview of my setup:
I've deployed my Django application on an Ubuntu server using nginx and gunicorn. My Django app worked perfectly when using it locally, but now on the server I only get this error when doing POST requests:
{
"detail": "CSRF Failed: CSRF token missing."
}
[This is the tutorial we followed to set everything up on our server] (https://www.digitalocean.com/community/tutorials/how-to-set-up-django-with-postgres-nginx-and-gunicorn-on-ubuntu)
For authentication we use django_auth_adfs (microsoft).
These are my setup files:
- nginx/sites-available/<our_project>:
server {
server_name <our_domain.com>;
ignore_invalid_headers off;
underscores_in_headers on;
location / {
root <location/to/frontend>;
}
location /oauth2/ {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location /login_redirect {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location ~ \.well-known {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location /api/ {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
location /admin/ {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/sel2-4.ugent.be/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/sel2-4.ugent.be/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = <our_domain.com>) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
server_name <our_domain.com>;
return 404; # managed by Certbot
}
- Settings.py file for django:
"""
Django settings for api project.
Generated by 'django-admin startproject' using Django 5.0.2.
For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from pathlib import Path
import os
from dotenv import load_dotenv
load_dotenv()
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['<our_domain.com>', 'localhost', '127.0.0.1']
# Application definition
INSTALLED_APPS = [
'rest_framework',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_auth_adfs',
'api',
]
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',
#'api.middleware.RedirectAnonymousUserMiddleware',
]
ROOT_URLCONF = 'api.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'api.wsgi.application'
# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': os.environ.get('DB_ENGINE'),
'NAME': os.environ.get('DB_NAME'),
'USER': os.environ.get('DB_USER'),
'PASSWORD': os.environ.get('DB_PASSWORD'),
'HOST': os.environ.get('DB_HOST'),
'PORT': os.environ.get('DB_PORT'),
}
}
# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')
CLIENT_ID = os.environ.get('CLIENT_ID')
CLIENT_SECRET = os.environ.get('CLIENT_SECRET')
TENANT_ID = os.environ.get('TENANT_ID')
AD_URL = os.environ.get('AD_URL')
AUTH_ADFS = {
'AUDIENCE': CLIENT_ID,
'CLIENT_ID': CLIENT_ID,
'CLIENT_SECRET': CLIENT_SECRET,
'CLAIM_MAPPING': {'first_name': 'given_name',
'last_name': 'family_name',
'email': 'upn'},
'GROUPS_CLAIM': 'roles',
'MIRROR_GROUPS': True,
'USERNAME_CLAIM': 'upn',
'TENANT_ID': TENANT_ID,
'RELYING_PARTY_ID': CLIENT_ID,
}
AUTHENTICATION_BACKENDS = [
'django_auth_adfs.backend.AdfsAuthCodeBackend',
]
LOGIN_URL = "django_auth_adfs:login"
LOGIN_REDIRECT_URL = "/login_redirect"
- This is one of the Django views that gives me the error (when sending a POST request we don't even enter this code block so the error is before the view):
@api_view(['GET', 'POST'])
def vak_list(request, format=None):
if request.method == 'GET':
if is_lesgever(request.user):
vakken = Vak.objects.all()
else:
vakken = Vak.objects.filter(studenten=request.user.id)
serializer = VakSerializer(vakken, many=True)
return Response(serializer.data)
elif request.method == 'POST':
if is_lesgever(request.user):
serializer = VakSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
return Response(status=status.HTTP_403_FORBIDDEN)
- All other config files are the default files. Request any other file if it's needed for more clarification.
Any help would be greatly appreciated, because I've been looking for a solution for quite a few hours now.
After testing without using Gunicorn, it's evident that Gunicorn isn't the cause of the issue. Additionally, when sending GET requests, the CSRF token is properly included in the Cookie headers, indicating that Nginx isn't stripping any crucial headers. However, the error persists only when users are logged in. While the temporary workaround is to have users remain logged out, this isn't a viable solution as users need to be logged in for the system to function correctly. The Cookie is being sent from the browser in the headers.