Carlos Curcino
Carlos Curcino

Reputation: 69

NextJs not setting the cookie from django csrf_token

My nextjs application integrated with django has an authentication system based on csrftoken and sessionid. Once the nextjs application runs, it makes a request to the backend for the csrf route, which automatically sets a cookie to the destination of the request. This works normally running locally. But when running nextjs in docker in the production environment you can see that the cookie is set in the response headers, but it does not actually create a cookie. I had seted CSRF_COOKIE_SAMESITE = None and SESSION_COOKIE_SAMESITE = None But still not setting the cookie, even in the headers showing the set cookie

XHRGET
https://api.viajahturismo.com.br/api/v1/csrf/
[HTTP/2 204  23ms]

        
    csrftoken   
    expires "2025-03-09T00:38:39.000Z"
    path    "/"
    samesite    "None"
    value   "8UayLGpyXUF0cc2AlM5zqBd4kdOHSfXf"
        
    csrftoken   "8UayLGpyXUF0cc2AlM5zqBd4kdOHSfXf"

My django setting:

"""
Django settings for myproject project.

Generated by 'django-admin startproject' using Django 4.2.1.

For more information on this file, see
https://docs.djangoproject.com/en/4.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.2/ref/settings/
"""

import os
from pathlib import Path

# 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/4.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

# ALLOWED_HOSTS = ['*']
# CSRF_TRUSTED_ORIGINS = ['*']

ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS').split(',') if os.getenv('ALLOWED_HOSTS') else []
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS').split(',')

# Application definition

INSTALLED_APPS = [
    'corsheaders',
    'viajah.apps.ViajahConfig',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'drf_yasg',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    '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',

    "whitenoise.middleware.WhiteNoiseMiddleware",
]

ROOT_URLCONF = 'myproject.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 = 'myproject.wsgi.application'


# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        '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/4.2/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/4.2/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/4.2/howto/static-files/

STATIC_URL = '/static/'
MEDIA_URL = '/media/'

CELERY_BROKER_URL = 'redis://redis:6379/0'
CELERY_RESULT_BACKEND = 'redis://redis:6379/0'

STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

MEDIA_ROOT = os.path.join(BASE_DIR, 'media')


LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
        'celery': {
            'handlers': ['console'],
            'level': os.getenv('DJANGO_LOG_LEVEL', 'INFO'),
        },
    },
}

# ---------------- Local Settings ---------------------------------------
# Put your local settings in mydjango directory to override this settings
# File name should be local_settings.py
try:
    from .local_settings import *
except ImportError:
    print('No Local Settings Found')

# ---------------- End Local Settings ------------------------------------


# # Default primary key field type
# # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

REST_FRAMEWORK = {
    "DEFAULT_PAGINATION_CLASS": 'rest_framework.pagination.PageNumberPagination',
    "PAGE_SIZE": 10,
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
}

AUTH_USER_MODEL = 'viajah.Usuario'

SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
CSRF_COOKIE_PATH = '/'
SESSION_COOKIE_PATH = '/'
CSRF_COOKIE_SAMESITE = None
SESSION_COOKIE_SAMESITE = None
SESSION_COOKIE_SECURE = True 
CSRF_COOKIE_SECURE = True

CORS_ALLOWED_ORIGINS = os.getenv('CORS_ALLOWED_ORIGINS').split(',')
CORS_ORIGIN_WHITELIST = os.getenv('CORS_ORIGIN_WHITELIST').split(',')
# CSRF_COOKIE_DOMAIN = os.getenv('CSRF_COOKIE_DOMAIN')

CORS_EXPOSE_HEADERS = ["Content-Type", "X-CSRFToken"]
CORS_ALLOW_CREDENTIALS = True

EMAIL_BACKEND = os.getenv('EMAIL_BACKEND')
EMAIL_HOST = os.getenv('EMAIL_HOST')
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER')
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD')
EMAIL_PORT = os.getenv('EMAIL_PORT')
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False

My nginx settings in production:

upstream lito_upstream {
  # ip_hash;
  server viajah-api:8000;
  server front_viajah:3000;

}

server {
    location / {
        proxy_pass http://viajah-api:8000/;
    }

    listen 8000;
    server_name viajah-api;
}

#server
server {
  #Defines the port on which the server will listen for requests.
  include /etc/nginx/mime.types;
  location /media/ {
      autoindex on;
      alias /app/media/;
  }
  location / {
      proxy_set_header Host $host;
      proxy_pass http://front_viajah:3000;
  }


  listen 3000;
  server_name front_viajah;

My view to get csrf

class get_csrf(APIView):
    authentication_classes = [SessionAuthentication]
    permission_classes = [AllowAny]
    @method_decorator(ensure_csrf_cookie)
    def get(self, request, *args, **kwargs) -> Response:
        return Response(status=status.HTTP_204_NO_CONTENT)

Ps: In localhost and running locally with docker it works really fine, the cookie is setted ok, and I can do the other requests that depends of csrfToken and sessionID. But in my production in a VPS server with Docker pulling the images from docker hub pushed by gh actions dont't work. My domains are mydomain.com.br and api.mydomain.com.br

Please if someone can help me, I don't have any idea of what is happening

Upvotes: 0

Views: 233

Answers (1)

Carlos Curcino
Carlos Curcino

Reputation: 69

The problem was because I had to add CSRF_COOKIE_DOMAIN and SESSION_COOKIE_DOMAIN to acept the domain, you have to add the domain whithout https://. If you not add these two options django will set host only for the cookies, which will only allow the cookies for api domain.

Upvotes: 0

Related Questions