I have created a vue and DRF application, which is working fine locally, it sets access, refresh and csrf token in cookies on login and so all of the isAuthenticated routes work fine.
But i handed it over to the person who's meant to deploy this on vps and now it doesn't set cookie on login. I'm able to login, but since no cookie is set, refreshing it logs me out and all routes give 401 error.
These are my django settings, removing secret_key here:
import os
from pathlib import Path
from datetime import timedelta
# 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/3.2/howto/deployment/checklist/
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ['*']
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'rest_framework_api_key',
'rest_framework_simplejwt.token_blacklist',
'corsheaders',
'django_filters',
'api'
]
AUTH_USER_MODEL = 'api.User'
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.AllowAllUsersModelBackend',
)
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',
]
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_HEADERS = [
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
"fxbNh4",
]
# CORS_ALLOWED_ORIGINS = [
# "http://localhost:8080"
# ]
# CORS_ALLOWED_ORIGINS = [
# 'http://34.67.29.99',
# ]
CORS_ALLOW_CREDENTIALS = True
ROOT_URLCONF = 'InstaBackend.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR)],
'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 = 'InstaBackend.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/3.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/3.2/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'Asia/Karachi'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.2/howto/static-files/
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 12,
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'api.authenticate.CustomAuthentication',
),
'DATETIME_FORMAT': "%Y-%m-%d %H:%M:%S",
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),
'REFRESH_TOKEN_LIFETIME': timedelta(days=7),
'ROTATE_REFRESH_TOKENS': False,
'BLACKLIST_AFTER_ROTATION': True,
'UPDATE_LAST_LOGIN': False,
'ALGORITHM': 'HS256',
'SIGNING_KEY': SECRET_KEY,
'VERIFYING_KEY': None,
'AUDIENCE': None,
'ISSUER': None,
'AUTH_HEADER_TYPES': ('Bearer',),
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION',
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
'USER_AUTHENTICATION_RULE': 'rest_framework_simplejwt.authentication.default_user_authentication_rule',
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),
'TOKEN_TYPE_CLAIM': 'token_type',
'JTI_CLAIM': 'jti',
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp',
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1),
'AUTH_ACCESS_COOKIE': 'access_token', # Cookie name. Enables cookies if value is set.
'AUTH_REFRESH_COOKIE': 'refresh_token', # Cookie name. Enables cookies if value is set.
'AUTH_CSRF_ACCESS_COOKIE': 'csrf_access_token', # Cookie name. Enables cookies if value is set.
'AUTH_CSRF_REFRESH_COOKIE': 'csrf_refresh_token', # Cookie name. Enables cookies if value is set.
'AUTH_COOKIE_DOMAIN': None, # A string like "example.com", or None for standard domain cookie.
'AUTH_COOKIE_SECURE': False, # Whether the auth cookies should be secure (https:// only).
'AUTH_COOKIE_HTTP_ONLY': False, # Http only cookie flag.It's not fetch by javascript.
'AUTH_COOKIE_PATH': '/', # The path of the auth cookie.
'AUTH_COOKIE_SAMESITE': None, # Whether to set the flag restricting cookie leaks on cross-site requests.
# This can be 'Lax', 'Strict', or None to disable the flag.
}
# CSRF_COOKIE_DOMAIN = '34.67.29.99'
# # CSRF_COOKIE_SECURE = True
# # USE_X_FORWARDED_HOST = True
# # SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# CSRF_TRUSTED_ORIGINS = ['http://34.67.29.99/','http://34.67.29.99',]
# CSRF_COOKIE_SAMESITE = 'Lax'
# CSRF_COOKIE_HTTPONLY = False
# Default primary key field type
# https://docs.djangoproject.com/en/3.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
This is how I set cookies :
def set_access_cookies(response, access_token):
response.set_cookie(
key=settings.SIMPLE_JWT['AUTH_ACCESS_COOKIE'],
value=access_token,
expires=settings.SIMPLE_JWT['ACCESS_TOKEN_LIFETIME'],
secure=settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
httponly=settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'],
samesite=settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE']
)
def set_refresh_cookies(response, refresh_token):
response.set_cookie(
key=settings.SIMPLE_JWT['AUTH_REFRESH_COOKIE'],
value=refresh_token,
expires=settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'],
secure=settings.SIMPLE_JWT['AUTH_COOKIE_SECURE'],
httponly=settings.SIMPLE_JWT['AUTH_COOKIE_HTTP_ONLY'],
samesite=settings.SIMPLE_JWT['AUTH_COOKIE_SAMESITE']
)
This is my login_view:
from django.middleware import csrf
from rest_framework.views import APIView
from rest_framework.response import Response
from django.contrib.auth import authenticate
from rest_framework import status
from api.util import (set_access_cookies, set_refresh_cookies, get_tokens_for_user, combine_role_permissions,
extract_patient_role)
from api.serializers import UserSerializer, RoleSerializer
from django.utils import timezone
from api.models import User
from rest_framework_api_key.permissions import HasAPIKey
from rest_framework.permissions import AllowAny
class LoginView(APIView):
authentication_classes = ()
permission_classes = (HasAPIKey,)
# permission_classes = (AllowAny,)
def post(self, request):
try:
data = request.data
response = Response()
username = data.get('username', None)
password = data.get('password', None)
try:
User.objects.get(email = username)
except User.DoesNotExist:
return Response({"msg": "User Credentails don't exist ! "}, status=status.HTTP_400_BAD_REQUEST)
user = authenticate(username=username, password=password)
if user.is_approved:
if user is not None:
role, roles = extract_patient_role(user.roles.all())
permissions = combine_role_permissions(roles)
data = get_tokens_for_user(user, is_patient=False)
set_access_cookies(response, data['access'])
set_refresh_cookies(response, data['refresh'])
csrf.get_token(request)
data = UserSerializer(user, context={'request': request}).data
data['roles'] = RoleSerializer(roles, many=True).data
data['permissions'] = permissions
response.status_code = status.HTTP_200_OK
response.data = {"msg": "Login successfully", "user": data}
user.last_login = timezone.now()
user.save()
return response
else:
return Response({"msg": "Invalid credentials"}, status=status.HTTP_404_NOT_FOUND)
else:
return Response({"msg": "Please verify account to log in"}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
print(e)
return Response({"msg": "Unable to Login"}, status=status.HTTP_400_BAD_REQUEST)
This is the axios instance for logging in:
import Vue from "vue";
import axios from "axios";
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== "") {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const axiosIns2 = axios.create({
baseURL: process.env.VUE_APP_API,
withCredentials: true,
xsrfCookieName: "csrftoken",
xsrfHeaderName: "X-CSRFTOKEN",
headers: {
"X-CSRFTOKEN": getCookie("csrftoken"),
Authorization: "Api-Key " + process.env.VUE_APP_KEY,
},
});
Vue.prototype.$http = axiosIns2;
export default axiosIns2;
And this is the axios instance for other authenticated requests:
import Vue from "vue";
import axios from "axios";
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== "") {
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === name + "=") {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
const axiosIns = axios.create({
baseURL: process.env.VUE_APP_API,
withCredentials: true,
xsrfCookieName: "csrftoken",
xsrfHeaderName: "X-CSRFTOKEN",
headers: {
"X-CSRFTOKEN": getCookie("csrftoken"),
},
});
const COOKIE_EXPIRED_MSG = "Token is invalid or expired";
axiosIns.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
let error_message = null;
if (error.response.data.messages) {
error_message = error.response.data.messages[0].message;
}
switch (error.response.status) {
case 401:
if (!error.config.retry && error_message === COOKIE_EXPIRED_MSG) {
error.config.retry = true;
// axiosIns.defaults.xsrfCookieName = "csrf_refresh_token";
await axiosIns.post("/refresh");
// axiosIns.defaults.xsrfCookieName = "csrf_access_token";
return axiosIns(error.config);
} else {
throw new Error("Error");
}
break;
default:
break;
}
return Promise.reject(error);
}
);
Vue.prototype.$http = axiosIns;
export default axiosIns;
VPS OS Details:
Ubuntu 18.04.6
I currently don't have access to VPS, i believe this issue is due to SIMPLE_JWT settings in settings.py.
The project runs perfectly fine with these settings locally, but it doesn't send cookies on login when hosted. It does give 200 on login though.
Hosted Via:
Nginx , Gunicorn
The backend and frontend are hosted on 2 different hosts,
backend at : http://34.67.29.99:8080/
Frontend at : http://instasearch.convpho.com/login
Related
I tried to send email via django Email message for account mail verification. When I send email via to console it send the activation link successfully but when it comes to sending via smtp I get TypeError: getaddrinfo() argument 1 must be string or none
Token generator
from django.contrib.auth.tokens import PasswordResetTokenGenerator
from django.utils import six
class TokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (
six.text_type(user.pk) + six.text_type(timestamp) +
six.text_type(user.is_active)
)
account_activation_token = TokenGenerator()
forms.py
from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User
class SignupForm(UserCreationForm):
email = forms.EmailField(max_length=200, help_text='Required')
class Meta:
model = User
fields = ('username', 'email', 'password1', 'password
"""
Django settings for project.
import os
from pathlib import Path
from django.contrib.messages import constants as messages
MESSAGE_TAGS = {
messages.DEBUG: 'alert-secondary',
messages.INFO: 'alert-info',
messages.SUCCESS: 'alert-success',
messages.WARNING: 'alert-warning',
messages.ERROR: 'alert-danger',
}
# 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/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = ''
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'dashboard.apps.DashboardConfig',
'user.apps.UserConfig',
'crispy_forms',
]
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',
]
ROOT_URLCONF = 'inventoryproject.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [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 = 'inventoryproject.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.mysql',
# 'NAME': 'inventory',
# 'USERNAME': 'root',
# 'HOST': 'localhost',
# 'PORT': 3306,
# 'PASSWORD': '', # Your Password
# }
# }
#
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/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/3.1/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
CRISPY_TEMPLATE_PACK = 'bootstrap4'
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = '/static/'
MEDIA_ROOT = (BASE_DIR/"media/")
MEDIA_URL = '/media/'
STATICFILES_DIRS = [
BASE_DIR / "static",
]
STATIC_ROOT = (BASE_DIR/"asert/")
LOGIN_REDIRECT_URL = 'dashboard-index'
LOGIN_URL = 'user-login'
# if DEBUG:
# EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
#
# else:
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com',
EMAIL_HOST_USER = 'clients.red#gmail.com',
EMAIL_HOST_PASSWORD = 'password1',
EMAIL_PORT = 587
def register(request):
if request.method == 'POST':
form = CreateUserForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.is_active = False
user = form.save()
group = Group.objects.get(name='Customers')
user.groups.add(group)
current_site = get_current_site(request)
mail_subject = "Activate your account."
message = {
'user': user,
'domain': current_site.domain,
'uid': urlsafe_base64_encode(force_bytes(user.pk)),
'token': account_activation_token.make_token(user),
}
message = get_template('acc_active_email.html').render(message)
to_email = form.cleaned_data.get('email')
print(to_email)
email = EmailMessage(mail_subject, message, to=[to_email])
email.content_subtype = "html"
import socket
socket.getaddrinfo('localhost', 25)
email.send()
error
TypeError at /register/
getaddrinfo() argument 1 must be string or None
Request Method: POST
Request URL: http://127.0.0.1:8000/register/
Django Version: 3.2
Exception Type: TypeError
Exception Value:
getaddrinfo() argument 1 must be string or None
Exception Location: C:\Users\ACME\AppData\Local\Programs\Python\Python39\lib\socket.py, line 954, in getaddrinfo
Python Executable: C:\Users\ACME\Desktop\inventory\inventory\real\Scripts\python.exe
Python Version: 3.9.10
Python Path:
['C:\\Users\\ACME\\Desktop\\inventory\\inventory',
'C:\\Users\\ACME\\AppData\\Local\\Programs\\Python\\Python39\\python39.zip',
'C:\\Users\\ACME\\AppData\\Local\\Programs\\Python\\Python39\\DLLs',
'C:\\Users\\ACME\\AppData\\Local\\Programs\\Python\\Python39\\lib',
'C:\\Users\\ACME\\AppData\\Local\\Programs\\Python\\Python39',
'C:\\Users\\ACME\\Desktop\\inventory\\inventory\\real',
'C:\\Users\\ACME\\Desktop\\inventory\\inventory\\real\\lib\\site-packages']
Server time: Sat, 11 Jun 2022 18:13:49 +0000
Well, you know that , changes string to tuple? Just delete it :)
EMAIL_HOST = 'smtp.gmail.com',
EMAIL_HOST_USER = 'clients.red#gmail.com',
EMAIL_HOST_PASSWORD = 'password1',
# should look like this:
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_HOST_USER = 'clients.red#gmail.com'
EMAIL_HOST_PASSWORD = 'password1'
And I think you will be good to go (I assume that you created app_password in Google account as changes came since 01.06 this year and it's not account passowrd)
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
}
Found it!
First make sure you got everything right in this answer:
https://stackoverflow.com/a/46412839/18327111
because django corsheaders middleware is checking the below if, make sure to have the following settings:
if conf.CORS_ALLOW_ALL_ORIGINS and not conf.CORS_ALLOW_CREDENTIALS:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = "*"
else:
response[ACCESS_CONTROL_ALLOW_ORIGIN] = origin
new settings:
from corsheaders.defaults import default_headers
CORS_ALLOW_ALL_ORIGINS= False
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
"http://yourip_or_domain:3000",
"http://127.0.0.1:3000",
"http://localhost:3000",
]
#making sure CORS_ALLOW_HEADERS is not "*"
CORS_ALLOW_HEADERS = list(default_headers) + ['Set-Cookie']
in case you are not using django session authentication(as i do) and want to bypass it add the following settings
CSRF_USE_SESSIONS = False
SESSION_COOKIE_SECURE = False
CSRF_COOKIE_SECURE = False
CSRF_COOKIE_SAMESITE = None
SESSION_COOKIE_SAMESITE = None
and the main reason it failed: this is copied from chrome developer tools and mozila also have this warning:
Because a cookie’s SameSite attribute was not set or is invalid, it
defaults to SameSite=Lax, which prevents the cookie from being sent in
a cross-site request. This behavior protects user data from
accidentally leaking to third parties and cross-site request forgery.
Resolve this issue by updating the attributes of the cookie:
Specify SameSite=None and Secure if the cookie should be sent in
cross-site requests. This enables third-party use.
Specify SameSite=Strict or SameSite=Lax if the cookie should not be
sent in cross-site requests.
so if you want to access cross origin, the only way is using an https and SameSite=None, otherwise you have to deploy your api and backend on the same domain
Note that http and https are considered as different domain and are cross-origin
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#samesitenone_requires_secure
Cookies from the same domain are no longer considered to be from the
same site if sent using a different scheme (http: or https:).
I have an issue regarding setting up Django and Vuejs in order to make them work together. My issue is regarding csrftoken and how this it is passed between Django and Vuejs.
Therefore, my setup is as follow:
Django is running on http://localhost:8000
Vuejs is running on http://localhost8080
I am using django rest framework and SessionAuthentication.
settings.py
"""
Django settings for core project.
Generated by 'django-admin startproject' using Django 3.1.2.
For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/
For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
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/3.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "8g+_gwm=s^kw69y3q55_p&n(_y^^fa!8a^(f7c)#d&md2617o9"
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = ["*"]
# Cors setup
# CORS_ORIGIN_ALLOW_ALL = False
# For CSRF
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
]
CSRF_TRUSTED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
]
# For Cookie
CORS_ORIGIN_WHITELIST = ("http://localhost:8080", "http://127.0.0.1:8080")
CORS_ALLOW_CREDENTIALS = True
# CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = False
SESSION_COOKIE_SAMESITE = "None"
CSRF_COOKIE_SAMESITE = None
SESSION_COOKIE_HTTPONLY = False
# Application definition
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"rest_framework",
"corsheaders",
"blog.apps.BlogConfig",
]
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",
]
ROOT_URLCONF = "core.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 = "core.wsgi.application"
# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
DATABASES = {
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": BASE_DIR / "db.sqlite3",
}
}
# Password validation
# https://docs.djangoproject.com/en/3.1/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/3.1/topics/i18n/
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/
STATIC_URL = "/static/"
# Django Rest Framework Authentication
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.SessionAuthentication",
],
"DEFAULT_PERMISSION_CLASSES": [
"rest_framework.permissions.IsAuthenticated",
],
}
serializers.py
class LoginSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
user = authenticate(username=attrs.get("username"), password=attrs.get("password"))
if not user:
raise serializers.ValidationError("Incorect email or password")
return {"user": user}
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email"]
views.py
class LoginView(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data.get("user")
login(request, user)
return Response(UserSerializer(user, context={"request": request}).data)
And this is the resource that I am trying to access:
views.py
class PostList(APIView):
""" Simple post view. """
def get(self, request):
post = Post.objects.all()
serializer = PostSerializer(post, many=True)
return Response(serializer.data)
def post(self, request):
serializer = PostSerializer(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)
Whenever I try to access this from frontend, I get the following error: Status Code: 403 Forbidden
Login.vuejs
<template>
<div class="home">
<h1>{{ message }}</h1>
<h2>Posts</h2>
<ul>
<li v-for="post in posts" :key="post.id">
{{ post.title }}
</li>
</ul>
<p>
<button #click="getPosts">Get Posts</button>
<button #click="createPost">Create Post</button>
<button #click="login">Login</button>
</p>
</div>
</template>
<script>
// # is an alias to /src
import axios from "axios";
// import Cookies from "js-cookie";
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFToken";
export default {
name: "Home",
data() {
return {
message: "",
posts: [],
};
},
methods: {
login: function() {
const baseUrl = "http://127.0.0.1:8000/rest-api/auth/";
axios
.post(baseUrl, { username: "madalin", password: "test" })
.then((response) => {
console.log(response);
});
},
createPost: function() {
const baseUrl = "http://127.0.0.1:8000/rest-api/posts/";
axios
.post(baseUrl, {
title: "Post title vuejs",
body: "This is a simple post body",
})
.then((response) => {
console.log(response);
});
},
getPosts: function() {
const baseUrl = "http://127.0.0.1:8000/rest-api/posts/";
axios
.get(baseUrl, {
withCredentials: true,
})
.then((response) => {
this.posts = response.data;
});
},
},
};
</script>
Now, whne I click on login, the response header is as follow:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: http://127.0.0.1:8080
Allow: POST, OPTIONS
Content-Length: 63
Content-Type: application/json
Date: Tue, 20 Oct 2020 22:22:51 GMT
Referrer-Policy: same-origin
Server: WSGIServer/0.2 CPython/3.8.5
Set-Cookie: csrftoken=6psCovwjDLVYzoZUDpiRDMjS6cQ6d8N9ubT3wNVjUZFAhmHinEFecYOHuWP2gC3J; expires=Tue, 19 Oct 2021 22:22:51 GMT; Max-Age=31449600; Path=/
Set-Cookie: sessionid=1txxggzki1r4s3cm0pc6faiityo36wep; expires=Tue, 03 Nov 2020 22:22:51 GMT; Max-Age=1209600; Path=/; SameSite=None
Vary: Accept, Cookie, Origin
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
As per my understanding, the csrftoken from response header should be send forward to Django with each new request. Request which is comming from axios.
Is there anything else that I can do/check?
I am struggling with this issue for the last 2 weeks at least, any help will be very appreciate.
Thank you,
I have managed to figure out what is the right setup in order to use SessionAuthentication with Django Rest Framework.
I will post the answer here maybe will help someone.
Step 1 - Make sure you have the following setup in your settings.py file
# For CORS and CSRF
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
CSRF_TRUSTED_ORIGINS = [
"http://localhost:8080",
"http://127.0.0.1:8080",
"http://localhost:8000",
"http://127.0.0.1:8000",
]
# For Cookie
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_HTTPONLY = True
# Since the frontend is on `http://localhost:8080` and the backend is on `http://localhost:8000`, they are two different origins and we need to set samesite to false
# UPDATE: Starting with django 3.1 this must be set to 'None'
SESSION_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SAMESITE = 'None'
# When you test this make sure both applications are on the same domain. Like `http://127.0.0.1:8000` and `http://127.0.0.1:8080`
Step 2 - Create the login and logout views
from django.contrib.auth import login, logout
from rest_framework import permissions, status
from rest_framework.response import Response
from rest_framework.views import APIView
class LoginView(APIView):
permission_classes = [permissions.AllowAny]
def post(self, request):
serializer = LoginSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data.get("user")
login(request, user)
return Response(UserSerializer(user, context={"request": request}).data)
class LogoutView(APIView):
permission_classes = [permissions.AllowAny]
def get(self, request):
logout(request=request)
return Response({"message": "You have been logout succesfully"})
Step 3 - Make sure that every request from frontend is made with credentials included
import axios from "axios";
const API = axios.create({
baseURL: `http://127.0.0.1:8000/rest-api/`,
headers: {
Accept: "application/json",
"Content-type": "application/json"
},
timeout: 10000,
withCredentials: true
});
export default {
login(payload) {
return API.post("auth/", payload);
},
logout() {
return API.get("logout/");
},
}
I am creating a Django REST project.
I created a custom user model in which I used rest-knox for token authentication. For the login and register user endpoints I used custom views where I authenticate the user's by their knox token. For password_change endpoint I used django auth's views.
Here is the problem I am facing:
When I try password_change endpoint in browsable API; it redirects me again to login page and does nothing. However Knox token for the user is created and returned in JSON.
When I try the endpoint in Postman it gives CSRF token error since I cannot get the CSRF token.
I tried to create my own front-end page for changing password and sometimes the 'csrftoken' was returned in cookie table of chrome but not all the time. Since I get the user token with JSON response I set it to cookies on React.js and reach anytime I want. Yet, get method of react-cookie doesn't work for csrftoken and it is being set as undefined. Finally I tried to use axios library for my post request and set default header for 'csrftoken' but couldn't see any results. The front-end 'localhost:3000/change_password/' page just redirects to itself by adding passed parameters to the end of url.
views.py
from django.shortcuts import render
from rest_framework import viewsets, generics
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
from knox.auth import TokenAuthentication
from knox.models import AuthToken
from rest_framework.permissions import IsAuthenticated, AllowAny
from .models import UserProfile
from .serializers import UserSerializer, LoginSerializer, KnoxSerializer, RegisterSerializer
from django.contrib.auth import views, decorators
from django.contrib.auth.decorators import user_passes_test
class UserListAPI(generics.ListAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
class UserDetailAPI(generics.RetrieveAPIView):
authentication_classes = (TokenAuthentication,)
permission_classes = (AllowAny,)
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
lookup_field = 'username'
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
token = AuthToken.objects.create(user)[1]
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": token
})
class RegisterAPI(generics.GenericAPIView):
serializer_class = RegisterSerializer
queryset = UserProfile.objects.all()
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.save()
return Response({
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1]
})
settings.py
import os
from decouple import config
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = config('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework',
'knox',
'rest_framework.authtoken',
'accounts',
'rest_auth',
]
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',
]
ROOT_URLCONF = 'Book_Lib_Project.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 = 'Book_Lib_Project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.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/3.0/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': ('knox.auth.TokenAuthentication',),
'DATETIME_FORMAT': ("%m/%d/%Y %H:%M:%S",),
'DEFAULT_PERMISSION_CLASSES':(
'rest_framework.permissions.AllowAny',),
}
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
# #"accounts.backends.EmailAuthenticationBackend",
]
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.Argon2PasswordHasher',
]
from datetime import timedelta
from rest_framework.settings import api_settings
REST_KNOX = {
'SECURE_HASH_ALGORITHM': 'cryptography.hazmat.primitives.hashes.SHA512',
'AUTH_TOKEN_CHARACTER_LENGTH': 64,
'TOKEN_TTL': timedelta(hours=10),
'USER_SERIALIZER': 'knox.serializers.UserSerializer',
'TOKEN_LIMIT_PER_USER': None,
'AUTO_REFRESH': False,
'EXPIRY_DATETIME_FORMAT': api_settings.DATETIME_FORMAT,
}
urls.py
from django.urls import path, include
from .views import UserListAPI, UserDetailAPI, LoginAPI, RegisterAPI, PasswordChangeAPI
from knox import views as knox_views
urlpatterns = [
path('logout/', knox_views.LogoutView.as_view(), name = "knox_logout"),
path('users/', UserListAPI.as_view()),
path('users/<str:username>/', UserDetailAPI.as_view()),
path('login/', LoginAPI.as_view()),
path('register/', RegisterAPI.as_view()),
path('', include('django.contrib.auth.urls')),
]
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import axios from 'axios';
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
axios.defaults.headers.post['Content-Type'] = 'application/json';
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
axios.interceptors.request.use(request => {
console.log(request.headers);
// Edit request config
return request;
}, error => {
console.log(error);
return Promise.reject(error);
});
axios.interceptors.response.use(response => {
console.log(response);
// Edit response config
return response;
}, error => {
console.log(error);
return Promise.reject(error);
});
ReactDOM.render(<App />, document.getElementById('root'));
serviceWorker.unregister();
ChangePassword.js
import React, {Component} from 'react';
//import ReactDOM from 'react-dom';
import { withCookies } from 'react-cookie';
import './ChangePassword.css';
import Axios from 'axios'
class ChangePassword extends Component {
state = {
credentials: {
old_password: '',
new_password1: '',
new_password2: '',
},
token: this.props.cookies.get('usertoken'),
}
inputChanged = event => {
let cred = this.state.credentials;
cred[event.target.name] = event.target.value;
this.setState({credentials: cred});
}
pressedEnter = event => {
if (event.key === 'Enter') {
this.changePassword();
}
}
changePassword = event => {
Axios.post('http://127.0.0.1:8000/accounts/password_change/', this.state.credentials, {headers: {
'Authorization': `Token ${this.state.token}`
}})
.then(res => {
console.log(res);
window.location.href = "/";
})
.catch( error => console.log(error))
}
render() {
return (
<div className="Login">
<form onSubmit={this.changePassword}>
<label>Old Password</label>
<input type="text" name="old_password" value={this.state.credentials.email} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<label>New Password</label>
<input type="password" name="new_password1" value={this.state.credentials.password} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<label>Confirm New Password</label>
<input type="password" name="new_password2" value={this.state.credentials.password} onChange={this.inputChanged} onKeyPress={this.pressedEnter}/><br/>
<input type="submit" value="Change" data-test="submit" />
</form>
</div>
)
}
}
export default withCookies(ChangePassword);
I solved the problem, so here is the solution if anyone encounters with the same problem and looks here.
At first, I needed to use the domain as http://127.0.0.1:3000 instead of http://localhost:3000 because of the restrictions given in https://curl.haxx.se/rfc/cookie_spec.html (check the section named as DOMAIN). Also, making some change on setting.py file was required.
Added below information to my settings.py file
CORS_ORIGIN_WHITELIST = (
'http://127.0.0.1:3000',
)
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CORS_EXPOSE_HEADERS = (
'Access-Control-Allow-Origin: http://127.0.0.1:3000',
)
Changed my axios configuration in index.js to
axios.defaults.withCredentials = true
axios.defaults.baseURL = 'http://127.0.0.1:8000/';
axios.defaults.xsrfCookieName = 'csrftoken'
axios.defaults.xsrfHeaderName = 'x-csrftoken'
A proper way to make a post for login page is like:
Axios.post('http://127.0.0.1:8000/accounts/login/', this.state.credentials).then(res => {
console.log(res);
this.props.cookies.set('usertoken', res.data.token);
// Used token based authentication. A token is returned from backend in JSON format.
}).catch( error => console.log(error))
I am trying to make a POST request from an Angular 6 app to a Django backend. Even though I include the csrf token in the headers, Django is logging a 403 Forbidden error as "CSRF token missing or incorrect". My code is as follows (with extraneous headers in my attempts to satisfy Django):
Angular Component:
import { Injectable } from '#angular/core';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { SharedService } from '../../shared.service';
import { CookieService } from 'ngx-cookie-service';
#Injectable({
providedIn: 'root'
})
export class EmailService {
// http options used for making any writing API calls with csrf token
private httpOptions: any;
csrfToken;
constructor(private http: HttpClient, private cookieService: CookieService) {
// Set the csrf token
this.http.get(SharedService.contactEmailUrl).subscribe((data) => (this.csrfToken = data['csrfToken']), (error1) => console.log(error1));
}
sendMailgunContactMessage(payload) {
// Configure CSRF token header options
this.httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': this.csrfToken,
'x-csrftoken': this.csrfToken,
'X-XSRF-TOKEN': this.csrfToken,
'XSRF-TOKEN': this.csrfToken,
'X-CSRF': this.csrfToken,
csrfmiddlewaretoken: this.csrfToken,
csrftoken: this.csrfToken
}),
withCredentials: true
};
let body = {
csrfmiddlewaretoken: this.csrfToken,
content: payload
};
return this.http.post(SharedService.contactEmailUrl, body, this.httpOptions);
}
}
Django Settings:
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'XYZ'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
CSRF_COOKIE_SECURE = False
CSRF_USE_SESSIONS = False
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'XYZ'
)
CORS_ALLOW_HEADERS = (
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'X-CSRFToken',
'x-csrftoken',
'X-XSRF-TOKEN',
'XSRF-TOKEN',
'csrfmiddlewaretoken',
'csrftoken',
'X-CSRF'
)
CORS_ALLOW_CREDENTIALS = True
# Application definition
INSTALLED_APPS = (
'corsheaders',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
)
MIDDLEWARE_CLASSES = (
'corsheaders.middleware.CorsMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.security.SecurityMiddleware',
)
MIDDLEWARE = (
'django.middleware.csrf.CsrfViewMiddleware'
)
ROOT_URLCONF = 'django_project.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 = 'django_project.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': 'debug.log',
},
},
'loggers': {
'django': {
'handlers': ['file'],
'level': 'DEBUG',
'propagate': True,
},
},
}
# Internationalization
# https://docs.djangoproject.com/en/1.8/topics/i18n/
LANGUAGE_CODE = 'en-us'
TIME_ZONE = 'UTC'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.8/howto/static-files/
STATIC_URL = '/static/'
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
# Allow Django from all hosts. This snippet is installed from
# /var/lib/digitalocean/allow_hosts.py
import os
import netifaces
# Find out what the IP addresses are at run time
# This is necessary because otherwise Gunicorn will reject the connections
def ip_addresses():
ip_list = []
for interface in netifaces.interfaces():
addrs = netifaces.ifaddresses(interface)
for x in (netifaces.AF_INET, netifaces.AF_INET6):
if x in addrs:
ip_list.append(addrs[x][0]['addr'])
return ip_list
# Discover our IP address
ALLOWED_HOSTS = ip_addresses()
Django View:
from django.shortcuts import render
from django.http import HttpResponse
from .controllers import *
import json
from django.middleware.csrf import get_token
from django.views.decorators.csrf import ensure_csrf_cookie
from django.views.decorators.csrf import csrf_protect
# Create your views here.
#ensure_csrf_cookie
def email(request):
if request.method == 'POST':
json_body = json.loads(request.body)
response = HttpResponse(send_contact_message(json_body))
return response
elif request.method == 'GET':
csrf_token = get_token(request)
response = HttpResponse('{ "csrfToken": "' + csrf_token + '" }')
return response
Note: Django is not setting a csrftoken cookie in cookies, not sure if that matters or not.
Why is the CSRF token(s) that I send back in the headers not being verified by Django?
Just import HttpClientXsrfModule to your project, it will take care of reading the cookie and resending it as a custom header in every request.
The cookie and header names are not a standard, but rather a convention, so you can configure them if the default ones don't match your backend's ones.
As it happens, Django's cookie name and header name don't match Angular default ones so HttpClientXsrfModule has to be imported withOptions like this:
import { HttpClientModule, HttpClientXsrfModule } from '#angular/common/http';
#NgModule({
...
imports:[..., HttpClientXsrfModule.withOptions({ cookieName: 'csrftoken', headerName: 'X-CSRFToken' }), ...]
...
})
try with this in your module instead of csrf headers
#NgModule({
providers: [ HttpXsrfInterceptor,
{ provide: HTTP_INTERCEPTORS, useExisting: HttpXsrfInterceptor, multi: true },
{ provide: HttpXsrfTokenExtractor, useClass: HttpXsrfCookieExtractor },
{ provide: XSRF_COOKIE_NAME, useValue: 'XSRF-TOKEN' },
{ provide: XSRF_HEADER_NAME, useValue: 'X-XSRF-TOKEN' }, ] })
replace the 'xsrf-token' and 'x-xsrf-token' with the value django sends back.
from: https://angular.io/api/common/http/HttpClientXsrfModule