I've created a DRF app which the backend is using jwt authentication with httpolnly cookies for authentication and it also uses a enforce_csrf for perventing csrf attacks.
from rest_framework_simplejwt.authentication import JWTAuthentication
from django.conf import settings
from rest_framework.authentication import CSRFCheck
from rest_framework import exceptions
def enforce_csrf(request):
check = CSRFCheck()
check.process_request(request)
reason = check.process_view(request, None, (), {})
print(reason)
if reason:
raise exceptions.PermissionDenied('CSRF Failed: %s' % reason)
class CookieBasedJWTAuthentication(JWTAuthentication):
def authenticate(self, request):
header = self.get_header(request)
if header is None:
raw_token = request.COOKIES.get(settings.SIMPLE_JWT['AUTH_COOKIE_ACCESS']) or None
else:
raw_token = self.get_raw_token(header)
if raw_token is None:
return None
validated_token = self.get_validated_token(raw_token)
enforce_csrf(request)
return self.get_user(validated_token), validated_token
to pervent csrf attacks django set a cookie and also a 'csrftokenmiddleware' and compares them.
here is the sample code for setting csrf cookie and token:
class SetCSRFToken(APIView):
permission_classes = [AllowAny]
def get(self, request):
response = Response()
csrf.get_token(request)
response.status_code = status.HTTP_200_OK
csrf_secret = csrf.get_token(request)
response.set_cookie(
key = 'csrftoken',
value = request.META["CSRF_COOKIE"],
expires = settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
path= settings.SIMPLE_JWT['AUTH_COOKIE_PATH'],
secure = settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
httponly = False,
samesite = 'Lax'
)
response.data = {"status": "CSRF cookie set", 'csrfmiddlewaretoken':request.META["CSRF_COOKIE"]}
return response
i also set :
CORS_ALLOW_ALL_ORIGINS= True
CORS_ALLOW_CREDENTIALS = True
the code works perfect when both frontend and backend are on the localhost, but when i run the backend on a remote server, the cookies are not set but the browser receives the Response to request.
when frontend is on the same host
when backend on remote server and frontend on localhsot
this is the console result for both cases
this is the settings, I've tried using CSRF options, but still not setting the cookie, what is weired is when i call out to my apiView from browser(rather than javascript) the cookie is being set.
settings.py:
"""
Django settings for core project.
Generated by 'django-admin startproject' using Django 4.0.2.
For more information on this file, see
https://docs.djangoproject.com/en/4.0/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/4.0/ref/settings/
"""
from pathlib import Path
import os
from .info import *
from datetime import timedelta
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
EMAIL_USE_TLS = EMAIL_USE_TLS
EMAIL_HOST = EMAIL_HOST
EMAIL_HOST_USER = EMAIL_HOST_USER
EMAIL_HOST_PASSWORD = EMAIL_HOST_PASSWORD
EMAIL_PORT = EMAIL_PORT
FRONTEND_URL = FRONTEND_URL
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-yzvg4k4r0%*3001&oo&up*@-yvcq(k@tpsdi^g=*ql#zvtogav'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['myip', 'localhost', '127.0.0.1']
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_SAMESITE = None
CSRF_TRUESTED_ORIGINS =['myip', 'localhost', '127.0.0.1']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'api',
'rest_framework',
'corsheaders',
'users',
'rest_framework_simplejwt.token_blacklist',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
"corsheaders.middleware.CorsMiddleware",
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'core.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'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 = 'core.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.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/4.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/4.0/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
CORS_ALLOW_ALL_ORIGINS= True
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
"http://myip:3000",
"http://127.0.0.1:3000",
"http://localhost:3000",
]
'''
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
)
}
'''
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
] ,
'DEFAULT_AUTHENTICATION_CLASSES': (
#'rest_framework_simplejwt.authentication.JWTAuthentication',
'users.authentication.CookieBasedJWTAuthentication',
)
}
AUTH_USER_MODEL = 'users.DearUser'
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'AUTH_HEADER_TYPES': ('Bearer','JWT'),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'USER_ID_FIELD': 'email',
'USER_ID_CLAIM': 'email',
# cookie based jwt settings
'AUTH_COOKIE_ACCESS':'ACCESS',
'AUTH_COOKIE_REFRESH':'REFRESH',
'AUTH_COOKIE_SECURE': False,
'AUTH_COOKIE_HTTP_ONLY' : True,
'AUTH_COOKIE_PATH': '/',
'AUTH_COOKIE_SAMESITE': 'None', #Strict
}